diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/.github/workflows/integration-csharp.yml b/.github/workflows/integration-csharp.yml new file mode 100644 index 000000000..3ad49f79a --- /dev/null +++ b/.github/workflows/integration-csharp.yml @@ -0,0 +1,14 @@ +name: Integrations (C#) +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Run tests + run: docker-compose run integration_csharp + + - name: Cleanup + if: always() + run: docker-compose down diff --git a/docker-compose.yml b/docker-compose.yml index dfa9d19e3..b7d6ea9e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -78,3 +78,15 @@ services: - HTTPBIN=true - INTEGRATION_CLIENT=shell - NODE_ENV=integration + + integration_csharp: + depends_on: + - reverse_proxy + build: + context: . + dockerfile: integrations/csharp.Dockerfile + command: 'npx jest src/integration.test.ts' + environment: + - HTTPBIN=true + - INTEGRATION_CLIENT=csharp + - NODE_ENV=integration diff --git a/integrations/csharp.Dockerfile b/integrations/csharp.Dockerfile new file mode 100644 index 000000000..c81b9ced0 --- /dev/null +++ b/integrations/csharp.Dockerfile @@ -0,0 +1,39 @@ +FROM node:20-alpine3.18 AS node +FROM mcr.microsoft.com/dotnet/sdk:7.0-alpine3.18 + +COPY integrations/https-cert/rootCA.pem /root/integration-test.pem + +# install the integration test certs +RUN apk --no-cache add ca-certificates && \ + rm -rf /var/cache/apk/* && \ + cp /root/integration-test.pem /usr/local/share/ca-certificates/ && \ + update-ca-certificates + +# copy node stuff from the node image to the dotnet image. Source for the +# necessary files: +# https://github.com/pyodide/pyodide/blob/1691d347d15a2c211cd49aebe6f15d42dfdf2369/Dockerfile#L105 +COPY --from=node /usr/local/bin/node /usr/local/bin/ +COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules +RUN ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \ + && ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx + +WORKDIR /src + +# - create a "hello world" project. We will later overwrite Program.cs in that +# folder with our test fixtures to run them +# - install RestSharp into that project +# - make a folder with the appropriate structure to hold the test fixtures +RUN dotnet new console -o IntTestCsharp -f net7.0 && \ + cd IntTestCsharp && \ + dotnet add package RestSharp && \ + mkdir -p /src/IntTestCsharp/src/fixtures/files + +# copy the only test fixture into the fixtures dir +ADD src/fixtures/files/hello.txt /src/IntTestCsharp/src/fixtures/files/ + +# add pacakge.json first so we don't have to `npm install` unless it changes +ADD package.json /src/ +RUN npm install + +# keep this last so that once this docker image is built it can be used quickly +ADD . /src diff --git a/src/fixtures/requests/multipart-form-data-no-params.js b/src/fixtures/requests/multipart-form-data-no-params.js index 51f5fa32d..cb307ba08 100644 --- a/src/fixtures/requests/multipart-form-data-no-params.js +++ b/src/fixtures/requests/multipart-form-data-no-params.js @@ -38,9 +38,7 @@ module.exports = { data: '', files: {}, form: {}, - headers: { - 'Content-Type': ['multipart/form-data'], - }, + headers: {}, json: null, method: 'POST', url: 'https://httpbin.org/anything', diff --git a/src/helpers/__snapshots__/utils.test.ts.snap b/src/helpers/__snapshots__/utils.test.ts.snap index 30479ecf2..a7fee7a91 100644 --- a/src/helpers/__snapshots__/utils.test.ts.snap +++ b/src/helpers/__snapshots__/utils.test.ts.snap @@ -31,6 +31,7 @@ exports[`availableTargets returns all available targets 1`] = ` "title": "Clojure", }, { + "cli": "dotnet", "clients": [ { "description": ".NET Standard HTTP Client", diff --git a/src/integration.test.ts b/src/integration.test.ts index 0679b23b1..c0cb17e40 100644 --- a/src/integration.test.ts +++ b/src/integration.test.ts @@ -1,5 +1,6 @@ /* eslint-disable jest/no-conditional-expect */ import type { Request } from '.'; +import type { AvailableTarget } from './helpers/utils'; import type { TargetId } from './targets/targets'; import type { Response } from 'har-format'; @@ -15,6 +16,7 @@ const expectedBasePath = ['src', 'fixtures', 'requests']; const ENVIRONMENT_CONFIG = { docker: { // Every client + target that we test in an HTTPBin-powered Docker environment. + csharp: ['httpclient', 'restsharp'], node: ['axios', 'fetch', 'native', 'request'], php: ['curl', 'guzzle'], python: ['requests'], @@ -31,6 +33,20 @@ const ENVIRONMENT_CONFIG = { }, }; +// Some environments are not as simple as `interpreter ` Here is where we +// put the instructions for how to run those environments in our docker +// containers +const EXEC_FUNCTION: Record Buffer> = { + csharp: (fixturePath: string) => { + // - copy the given fixture into a file called Program.cs - c# expects + // there to be only one file with top-level code in any project, so we'll + // keep this project around but copy each fixture in as Program.cs + // - run Program.cs and return the output + shell.execSync(`cp ${fixturePath} /src/IntTestCsharp/Program.cs`); + return shell.execSync('cd /src/IntTestCsharp && dotnet run Program.cs'); + }, +}; + const inputFileNames = readdirSync(path.join(...expectedBasePath), 'utf-8'); const fixtures: [string, Request][] = inputFileNames.map(inputFileName => [ @@ -96,9 +112,10 @@ function looseJSONParse(obj: any) { availableTargets() .filter(target => target.cli) .filter(testFilter('key', environmentFilter())) - .forEach(({ key: targetId, cli: targetCLI, title, extname: fixtureExtension, clients }) => { + .forEach(target => { + const { key: targetId, title, clients } = target; (process.env.NODE_ENV === 'test' ? describe.skip : describe)(`${title} integration tests`, () => { - clients.filter(testFilter('key', clientFilter(targetId))).forEach(({ key: clientId }) => { + clients.filter(testFilter('key', clientFilter(target.key))).forEach(({ key: clientId }) => { // If we're in an HTTPBin-powered Docker environment we only want to run tests for the // client that our Docker has been configured for. if (process.env.HTTPBIN && process.env.INTEGRATION_CLIENT !== targetId) { @@ -108,178 +125,215 @@ availableTargets() // eslint-disable-next-line jest/valid-title describe(clientId, () => { fixtures.filter(testFilter(0, fixtureIgnoreFilter, true)).forEach(([fixture, request]) => { - it(`should return the expected response for \`${fixture}\``, () => { - const basePath = path.join( - 'src', - 'targets', - targetId, - clientId, - 'fixtures', - `${fixture}${extname(targetId)}` - ); - - // Clone the fixture we're testing against to another object because for multipart cases - // we're deleting the header, and if we don't clone the fixture to another object, that - // deleted header will cause other tests to fail because it's missing where other tests - // are expecting it. - const har = JSON.parse(JSON.stringify(request)); - const url = har.log.entries[0].request.url; - const harResponse = har.log.entries[0].response as Response; - - const command = format(targetCLI, basePath); - let stdout; - try { - stdout = shell.execSync(command); - } catch (err) { - // If this target throws errors when it can't access a method on the server that - // doesn't exist let's make sure that it only did that on the `custom-method` test, - // otherwise something went wrong! - if (err.message.includes('405 METHOD NOT ALLOWED')) { - expect(fixture).toBe('custom-method'); - return; - } - - throw err; - } - - stdout = stdout.toString().trim(); - - // If the endpoint we're testing against returns HTML we should do a string comparison - // instead of parsing a non-existent JSON response. - if (harResponse.headers.find(header => header.name === 'Content-Type' && header.value === 'text/html')) { - // const stdoutTrimmed = stdout.toString().trim(); - - try { - expect(stdout).toStrictEqual(harResponse.content.text); - } catch (err) { - // Some targets always assume that their response is JSON and for this case - // (`custom-method`) will print out an empty string instead. - expect(stdout).toBe(''); - } - - return; - } - - const expected = JSON.parse(String(harResponse.content.text)); - let response; - try { - response = JSON.parse(stdout); - } catch (err) { - // Some JS targets print out their response with `console.log(json)` which creates - // a JSON object that we can't access with `JSON.parse()`. - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval! - if (fixtureExtension !== '.js') { - throw err; - } - - response = looseJSONParse(stdout); - } - - // If we're testing against the `/cookies` or `/headers` endpoints it returns a - // different schema than everything else. - if (url === 'https://httpbin.org/cookies') { - expect(response.cookies).toStrictEqual(expected.cookies); - return; - } else if (url === 'https://httpbin.org/headers') { - expect(response.headers).toStrictEqual( - expect.objectContaining({ - ...expected.headers, - }) - ); - - return; - } - - expect(response.args).toStrictEqual(expected.args); - - // Some targets send files that have a new line at the end of them without that new - // line so we need to make our assertion universal across all targets. - let files = {}; - if (Object.keys(response.files || {}).length) { - files = Object.entries(response.files) - .map(([k, v]) => ({ [k]: String(v).trim() })) - .reduce((prev, next) => Object.assign(prev, next)); - } - - expect(files).toStrictEqual(expected.files); - - expect(response.form || {}).toStrictEqual(expected.form); - expect(response.method).toStrictEqual(expected.method); - expect(response.url).toStrictEqual(expected.url); - - // Because some JS targets may be returning their payloads with `console.log()` that - // method has a default depth, at which point it turns objects into `[Object]`. When - // we then run that through `looseJSONParse` it gets transformed again into - // `[ [Function: Object] ]`. Since we don't have access to the original object context - // from the target snippet, we rewrite our response a bit so that it can partially - // match what we're looking for. - // - // Of course the side effect to this is is that now these test cases may be subject - // to flakiness but without updating the root snippets to not use `console.log()`, - // which we don't want to do, this is the way it's got to be. - if (fixture === 'application-json' && fixtureExtension === '.js') { - const respJSON = response.json; - respJSON.arr_mix[2] = { arr_mix_nested: [] }; - - expect(respJSON).toStrictEqual(expected.json); - } else { - expect(response.json).toStrictEqual(expected.json); - } - - const expectJson = expected.headers?.['Content-Type']?.[0].includes('application/json'); - const expectMultipart = expected.headers?.['Content-Type']?.[0].includes('multipart/form-data'); - - // If we're dealing with a JSON payload, some snippets add indents and new lines to - // the data that is sent to - // HTTPBin (that it then returns back us in the same format) -- to make this `data` - // check target agnostic we need to parse and re-stringify our expectations so that - // this test can universally match them all. - if (expectJson) { - // In our postdata-malformed fixture we're sending a POST payload without any - // content so what HTTPBin sends back to us is a `json: null` and `data: ''`, which - // we need to specially assert here as running `JSON.parse()` on an empty string - // will throw an exception. - if (fixture === 'postdata-malformed' && response.data === '') { - expect(expected.data).toBe(''); - } else { - expect(JSON.stringify(JSON.parse(response.data))).toStrictEqual( - JSON.stringify(JSON.parse(expected.data)) - ); - } - // httpbin-go includes multipart/form-data in the `data` response - // field, which I think is sensible. In this case, the response - // includes a randomly-generated boundary and is difficult to - // sensibly match against, so don't check the data attribute - } else if (!expectMultipart) { - expect(response.data).toStrictEqual(expected.data); - } - - // `multipart/form-data` needs some special tests to assert that boundaries were sent - // and received properly. - if (expectMultipart) { - if (expected.headers['Content-Type'][0] !== response.headers['Content-Type'][0]) { - // If the headers match identically, great! If not we need to make sure that - // there's a boundary set up. It doesn't matter that the - // /right/ boundary is set up because some targets may add - // their own, we just need to make sure that **a** boundary - // is present. - const contentTypes: string[] = response.headers['Content-Type'][0] - .split(';') - .map((p: string) => p.trim()); - - expect(contentTypes).toHaveLength(2); - expect(contentTypes.map(type => type.includes('boundary=')).filter(Boolean)).toHaveLength(1); - } - } else { - expect(response.headers).toStrictEqual( - expect.objectContaining({ - ...expected.headers, - }) - ); - } - }); + if (fixture === 'custom-method' && clientId === 'restsharp') { + // restsharp doesn't even let you express calling an invalid + // method, there's no point in testing it against this particular + // fixture + return; + } + + integrationTest(clientId, target, fixture, request); }); }); }); }); }); + +function integrationTest( + clientId: string, + { key: targetId, cli: targetCLI, extname: fixtureExtension }: AvailableTarget, + fixture: string, + request: Request +) { + test(`should return the expected response for \`${fixture}\``, () => { + const basePath = path.join('src', 'targets', targetId, clientId, 'fixtures', `${fixture}${extname(targetId)}`); + + // Clone the fixture we're testing against to another object because for multipart cases + // we're deleting the header, and if we don't clone the fixture to another object, that + // deleted header will cause other tests to fail because it's missing where other tests + // are expecting it. + const har = JSON.parse(JSON.stringify(request)); + const url = har.log.entries[0].request.url; + const harResponse = har.log.entries[0].response as Response; + + let stdout; + try { + // If there's a runner function, use that; otherwise just call + // + if (EXEC_FUNCTION[targetId]) { + stdout = EXEC_FUNCTION[targetId](basePath); + } else { + stdout = shell.execSync(format(targetCLI, basePath)); + } + } catch (err) { + // If this target throws errors when it can't access a method on the server that + // doesn't exist let's make sure that it only did that on the `custom-method` test, + // otherwise something went wrong! + if (err.message.toLowerCase().match(/405/)) { + expect(fixture).toBe('custom-method'); + return; + } + + throw err; + } + + stdout = stdout.toString().trim(); + + // If the endpoint we're testing against returns HTML we should do a string comparison + // instead of parsing a non-existent JSON response. + if (harResponse.headers.find(header => header.name === 'Content-Type' && header.value === 'text/html')) { + // const stdoutTrimmed = stdout.toString().trim(); + + try { + expect(stdout).toStrictEqual(harResponse.content.text); + } catch (err) { + // Some targets always assume that their response is JSON and for this case + // (`custom-method`) will print out an empty string instead. + expect(stdout).toBe(''); + } + + return; + } + + const expected = JSON.parse(String(harResponse.content.text)); + let response: any; + try { + response = JSON.parse(stdout); + } catch (err) { + // Some JS targets print out their response with `console.log(json)` which creates + // a JSON object that we can't access with `JSON.parse()`. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval! + if (fixtureExtension !== '.js') { + throw err; + } + + response = looseJSONParse(stdout); + } + + // If we're testing against the `/cookies` or `/headers` endpoints it returns a + // different schema than everything else. + if (url === 'https://httpbin.org/cookies') { + expect(response.cookies).toStrictEqual(expected.cookies); + return; + } else if (url === 'https://httpbin.org/headers') { + expect(response.headers).toStrictEqual( + expect.objectContaining({ + ...expected.headers, + }) + ); + + return; + } + + expect(response.args).toStrictEqual(expected.args); + + // Some targets send files that have a new line at the end of them without that new + // line so we need to make our assertion universal across all targets. + let files = {}; + if (Object.keys(response.files || {}).length) { + files = Object.entries(response.files) + .map(([k, v]) => ({ [k]: String(v).trim() })) + .reduce((prev, next) => Object.assign(prev, next)); + } + + expect(files).toStrictEqual(expected.files); + + expect(response.form || {}).toStrictEqual(expected.form); + expect(response.method).toStrictEqual(expected.method); + expect(response.url).toStrictEqual(expected.url); + + // Because some JS targets may be returning their payloads with `console.log()` that + // method has a default depth, at which point it turns objects into `[Object]`. When + // we then run that through `looseJSONParse` it gets transformed again into + // `[ [Function: Object] ]`. Since we don't have access to the original object context + // from the target snippet, we rewrite our response a bit so that it can partially + // match what we're looking for. + // + // Of course the side effect to this is is that now these test cases may be subject + // to flakiness but without updating the root snippets to not use `console.log()`, + // which we don't want to do, this is the way it's got to be. + if (fixture === 'application-json' && fixtureExtension === '.js') { + const respJSON = response.json; + respJSON.arr_mix[2] = { arr_mix_nested: [] }; + + expect(respJSON).toStrictEqual(expected.json); + } else { + expect(response.json).toStrictEqual(expected.json); + } + + const expectJson = expected.headers?.['Content-Type']?.[0].includes('application/json'); + // if there is a request header containing "multipart/form-data", we're + // expecting a multipart response. We can't check the expected headers here + // because in the case of multipart-form-data-no-params, we don't want to + // assert that the request contained a content-type, since there was no + // content + const expectMultipart = har.log.entries[0].request.headers + ?.find((x: { name: string; value: string }) => x.name.toLowerCase().includes('content-type')) + ?.value?.includes('multipart/form-data'); + + // If we're dealing with a JSON payload, some snippets add indents and new lines to + // the data that is sent to + // HTTPBin (that it then returns back us in the same format) -- to make this `data` + // check target agnostic we need to parse and re-stringify our expectations so that + // this test can universally match them all. + if (expectJson) { + // In our postdata-malformed fixture we're sending a POST payload without any + // content so what HTTPBin sends back to us is a `json: null` and `data: ''`, which + // we need to specially assert here as running `JSON.parse()` on an empty string + // will throw an exception. + if (fixture === 'postdata-malformed' && response.data === '') { + expect(expected.data).toBe(''); + } else { + expect(JSON.stringify(JSON.parse(response.data))).toStrictEqual(JSON.stringify(JSON.parse(expected.data))); + } + // httpbin-go includes multipart/form-data in the `data` response + // field, which I think is sensible. In this case, the response + // includes a randomly-generated boundary and is difficult to + // sensibly match against, so don't check the data attribute + } else if (!expectMultipart) { + expect(response.data).toStrictEqual(expected.data); + } + + // `multipart/form-data` needs some special tests to assert that boundaries were sent + // and received properly. + if (expectMultipart) { + // if the Content type headers don't match identically, check that there + // is a boundary present in the data. If they do match exactly, no need + // to do anything; we tested what we wanted + // + // Except the "multipart-form-data-no-params" fixture, because in this + // test there is no content and so libraries should not be required to + // send a content-type header + if ( + expected.headers['Content-Type']?.[0] !== response.headers['Content-Type']?.[0] && + fixture !== 'multipart-form-data-no-params' + ) { + const contentTypes: string[] = response.headers['Content-Type'][0].split(';').map((p: string) => p.trim()); + + expect(contentTypes).toHaveLength(2); + expect(contentTypes.map(type => type.includes('boundary=')).filter(Boolean)).toHaveLength(1); + } + } else { + // Content-type headers particularly may contain the text-encoding, so we + // can't check for exact equality. For example, "Content-Type: + // text/plain; charset=utf-8" is perfectly valid and we don't want to + // fail it for not having the `text/plain` content type. In the future, + // we may want to try and be more smart about parsing the header value, + // but for now, just check that the expected header value is contained + // anywhere within the received header + const headers = expected.headers as Record; + Object.entries(headers).forEach(([name, value]) => { + // In the postdata-malformed fixture, we're sending a POST without any + // body. Some libraries absolutely refuse to add an `application/json` + // content-type header for a request without a body, which I think is + // sensible. Allow those cases to pass rather than going long miles to + // force libraries to act stupidly. + if (name === 'Content-Type' && fixture === 'postdata-malformed') { + return; + } + expect(response.headers).toHaveProperty(name); + expect(response.headers[name][0]).toContain(value[0]); + }); + } + }); +} diff --git a/src/targets/csharp/restsharp/client.ts b/src/targets/csharp/restsharp/client.ts index 20fa85cf3..1dfed363c 100644 --- a/src/targets/csharp/restsharp/client.ts +++ b/src/targets/csharp/restsharp/client.ts @@ -44,7 +44,11 @@ export const restsharp: Client = { // if we have post data, restsharp really wants to set the contentType // itself; do not add a content-type header or you end up with failures // which manifest as unhandled exceptions. - if (postData.mimeType && key.toLowerCase() === 'content-type') { + // + // The only case where we _do_ want to add it is if there's no postData + // text, in which case there will be no `AddJsonBody` call, and restsharp + // won't know to set the content type + if (postData.mimeType && key.toLowerCase() === 'content-type' && postData.text) { if (isMultipart && postData.boundary) { push(`request.FormBoundary = "${postData.boundary}";`); } @@ -87,7 +91,7 @@ export const restsharp: Client = { push(`var response = await client.${title(method)}Async(request);\n`); - push('Console.WriteLine("{0}", response.Content);'); + push('Console.WriteLine("{0}", response.Content);\n'); return join(); }, diff --git a/src/targets/csharp/restsharp/fixtures/application-form-encoded.cs b/src/targets/csharp/restsharp/fixtures/application-form-encoded.cs index c1acb2e73..9e02cbc5a 100644 --- a/src/targets/csharp/restsharp/fixtures/application-form-encoded.cs +++ b/src/targets/csharp/restsharp/fixtures/application-form-encoded.cs @@ -8,4 +8,4 @@ request.AddParameter("hello", "world"); var response = await client.PostAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/application-json.cs b/src/targets/csharp/restsharp/fixtures/application-json.cs index 589036384..e17c726e3 100644 --- a/src/targets/csharp/restsharp/fixtures/application-json.cs +++ b/src/targets/csharp/restsharp/fixtures/application-json.cs @@ -7,4 +7,4 @@ request.AddJsonBody("{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":[]}],\"boolean\":false}", false); var response = await client.PostAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/cookies.cs b/src/targets/csharp/restsharp/fixtures/cookies.cs index 1bedc6df1..0a35af786 100644 --- a/src/targets/csharp/restsharp/fixtures/cookies.cs +++ b/src/targets/csharp/restsharp/fixtures/cookies.cs @@ -8,4 +8,4 @@ request.AddCookie("bar", "baz", "/cookies", "httpbin.org"); var response = await client.GetAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/full.cs b/src/targets/csharp/restsharp/fixtures/full.cs index aa3fd11d7..c31a1260c 100644 --- a/src/targets/csharp/restsharp/fixtures/full.cs +++ b/src/targets/csharp/restsharp/fixtures/full.cs @@ -10,4 +10,4 @@ request.AddParameter("foo", "bar"); var response = await client.PostAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/headers.cs b/src/targets/csharp/restsharp/fixtures/headers.cs index 6cdb2c4be..94c8e7d0e 100644 --- a/src/targets/csharp/restsharp/fixtures/headers.cs +++ b/src/targets/csharp/restsharp/fixtures/headers.cs @@ -10,4 +10,4 @@ request.AddHeader("quoted-value", "\"quoted\" 'string'"); var response = await client.GetAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/http-insecure.cs b/src/targets/csharp/restsharp/fixtures/http-insecure.cs index 79986efb8..622eba335 100644 --- a/src/targets/csharp/restsharp/fixtures/http-insecure.cs +++ b/src/targets/csharp/restsharp/fixtures/http-insecure.cs @@ -6,4 +6,4 @@ var request = new RestRequest(""); var response = await client.GetAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/jsonObj-multiline.cs b/src/targets/csharp/restsharp/fixtures/jsonObj-multiline.cs index 2bd85bd7a..d6e78abb5 100644 --- a/src/targets/csharp/restsharp/fixtures/jsonObj-multiline.cs +++ b/src/targets/csharp/restsharp/fixtures/jsonObj-multiline.cs @@ -7,4 +7,4 @@ request.AddJsonBody("{\n \"foo\": \"bar\"\n}", false); var response = await client.PostAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/jsonObj-null-value.cs b/src/targets/csharp/restsharp/fixtures/jsonObj-null-value.cs index a9a5ca673..426281408 100644 --- a/src/targets/csharp/restsharp/fixtures/jsonObj-null-value.cs +++ b/src/targets/csharp/restsharp/fixtures/jsonObj-null-value.cs @@ -7,4 +7,4 @@ request.AddJsonBody("{\"foo\":null}", false); var response = await client.PostAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/multipart-data.cs b/src/targets/csharp/restsharp/fixtures/multipart-data.cs index b256b6997..d85aeaa89 100644 --- a/src/targets/csharp/restsharp/fixtures/multipart-data.cs +++ b/src/targets/csharp/restsharp/fixtures/multipart-data.cs @@ -10,4 +10,4 @@ request.AddParameter("bar", "Bonjour le monde"); var response = await client.PostAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/multipart-file.cs b/src/targets/csharp/restsharp/fixtures/multipart-file.cs index d1fda9b99..f466a53f5 100644 --- a/src/targets/csharp/restsharp/fixtures/multipart-file.cs +++ b/src/targets/csharp/restsharp/fixtures/multipart-file.cs @@ -9,4 +9,4 @@ request.AddFile("foo", "src/fixtures/files/hello.txt"); var response = await client.PostAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/multipart-form-data-no-params.cs b/src/targets/csharp/restsharp/fixtures/multipart-form-data-no-params.cs index f52b66da6..43b0ce7f1 100644 --- a/src/targets/csharp/restsharp/fixtures/multipart-form-data-no-params.cs +++ b/src/targets/csharp/restsharp/fixtures/multipart-form-data-no-params.cs @@ -5,6 +5,7 @@ var client = new RestClient(options); var request = new RestRequest(""); request.AlwaysMultipartFormData = true; +request.AddHeader("Content-Type", "multipart/form-data"); var response = await client.PostAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/multipart-form-data.cs b/src/targets/csharp/restsharp/fixtures/multipart-form-data.cs index 2456020a1..545f75c3a 100644 --- a/src/targets/csharp/restsharp/fixtures/multipart-form-data.cs +++ b/src/targets/csharp/restsharp/fixtures/multipart-form-data.cs @@ -9,4 +9,4 @@ request.AddParameter("foo", "bar"); var response = await client.PostAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/nested.cs b/src/targets/csharp/restsharp/fixtures/nested.cs index 57b8ffb28..28b1499e3 100644 --- a/src/targets/csharp/restsharp/fixtures/nested.cs +++ b/src/targets/csharp/restsharp/fixtures/nested.cs @@ -6,4 +6,4 @@ var request = new RestRequest(""); var response = await client.GetAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/postdata-malformed.cs b/src/targets/csharp/restsharp/fixtures/postdata-malformed.cs index 147e990fa..652350ce8 100644 --- a/src/targets/csharp/restsharp/fixtures/postdata-malformed.cs +++ b/src/targets/csharp/restsharp/fixtures/postdata-malformed.cs @@ -4,6 +4,7 @@ var options = new RestClientOptions("https://httpbin.org/anything"); var client = new RestClient(options); var request = new RestRequest(""); +request.AddHeader("content-type", "application/json"); var response = await client.PostAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/query-encoded.cs b/src/targets/csharp/restsharp/fixtures/query-encoded.cs index f60e554dc..02c55cf47 100644 --- a/src/targets/csharp/restsharp/fixtures/query-encoded.cs +++ b/src/targets/csharp/restsharp/fixtures/query-encoded.cs @@ -6,4 +6,4 @@ var request = new RestRequest(""); var response = await client.GetAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/query.cs b/src/targets/csharp/restsharp/fixtures/query.cs index bca7ca7b7..88b0ee5be 100644 --- a/src/targets/csharp/restsharp/fixtures/query.cs +++ b/src/targets/csharp/restsharp/fixtures/query.cs @@ -6,4 +6,4 @@ var request = new RestRequest(""); var response = await client.GetAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/short.cs b/src/targets/csharp/restsharp/fixtures/short.cs index 76ad84cec..9ffe93ecf 100644 --- a/src/targets/csharp/restsharp/fixtures/short.cs +++ b/src/targets/csharp/restsharp/fixtures/short.cs @@ -6,4 +6,4 @@ var request = new RestRequest(""); var response = await client.GetAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/restsharp/fixtures/text-plain.cs b/src/targets/csharp/restsharp/fixtures/text-plain.cs index 20d61e5b7..62d226580 100644 --- a/src/targets/csharp/restsharp/fixtures/text-plain.cs +++ b/src/targets/csharp/restsharp/fixtures/text-plain.cs @@ -7,4 +7,4 @@ request.AddStringBody("Hello World", "text/plain"); var response = await client.PostAsync(request); -Console.WriteLine("{0}", response.Content); \ No newline at end of file +Console.WriteLine("{0}", response.Content); diff --git a/src/targets/csharp/target.ts b/src/targets/csharp/target.ts index fbf6d519e..580139760 100644 --- a/src/targets/csharp/target.ts +++ b/src/targets/csharp/target.ts @@ -9,6 +9,7 @@ export const csharp: Target = { title: 'C#', extname: '.cs', default: 'restsharp', + cli: 'dotnet', }, clientsById: {