diff options
Diffstat (limited to 'devtools/client/netmonitor/test/browser_net_copy_as_curl.js')
-rw-r--r-- | devtools/client/netmonitor/test/browser_net_copy_as_curl.js | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/test/browser_net_copy_as_curl.js b/devtools/client/netmonitor/test/browser_net_copy_as_curl.js new file mode 100644 index 0000000000..4cce679fe9 --- /dev/null +++ b/devtools/client/netmonitor/test/browser_net_copy_as_curl.js @@ -0,0 +1,242 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests if Copy as cURL works. + */ + +const POST_PAYLOAD = "Plaintext value as a payload"; + +add_task(async function () { + const { tab, monitor } = await initNetMonitor(HTTPS_CURL_URL, { + requestCount: 1, + }); + info("Starting test... "); + + // Different quote chars are used for Windows and POSIX + const QUOTE_WIN = '"'; + const QUOTE_POSIX = "'"; + + const isWin = Services.appinfo.OS === "WINNT"; + const testData = isWin + ? [ + { + menuItemId: "request-list-context-copy-as-curl-win", + data: buildTestData(QUOTE_WIN), + }, + { + menuItemId: "request-list-context-copy-as-curl-posix", + data: buildTestData(QUOTE_POSIX), + }, + ] + : [ + { + menuItemId: "request-list-context-copy-as-curl", + data: buildTestData(QUOTE_POSIX), + }, + ]; + + await testForPlatform(tab, monitor, testData); + + await teardown(monitor); +}); + +function buildTestData(QUOTE) { + // Quote a string, escape the quotes inside the string + function quote(str) { + return QUOTE + str.replace(new RegExp(QUOTE, "g"), `\\${QUOTE}`) + QUOTE; + } + + // Header param is formatted as -H "Header: value" or -H 'Header: value' + function header(h) { + return "-H " + quote(h); + } + + // Construct the expected command + const SIMPLE_BASE = ["curl " + quote(HTTPS_SIMPLE_SJS)]; + const SLOW_BASE = ["curl " + quote(HTTPS_SLOW_SJS)]; + const BASE_RESULT = [ + "--compressed", + header("User-Agent: " + navigator.userAgent), + header("Accept: */*"), + header("Accept-Language: " + navigator.language), + header("X-Custom-Header-1: Custom value"), + header("X-Custom-Header-2: 8.8.8.8"), + header("X-Custom-Header-3: Mon, 3 Mar 2014 11:11:11 GMT"), + header("Referer: " + HTTPS_CURL_URL), + header("Connection: keep-alive"), + header("Pragma: no-cache"), + header("Cache-Control: no-cache"), + header("Sec-Fetch-Dest: empty"), + header("Sec-Fetch-Mode: cors"), + header("Sec-Fetch-Site: same-origin"), + ]; + + const COOKIE_PARTIAL_RESULT = [header("Cookie: bob=true; tom=cool")]; + + const POST_PARTIAL_RESULT = [ + "-X", + "POST", + "--data-raw " + quote(POST_PAYLOAD), + header("Content-Type: text/plain;charset=UTF-8"), + ]; + const ORIGIN_RESULT = [header("Origin: https://example.com")]; + + const HEAD_PARTIAL_RESULT = ["-I"]; + + return { + SIMPLE_BASE, + SLOW_BASE, + BASE_RESULT, + COOKIE_PARTIAL_RESULT, + POST_PAYLOAD, + POST_PARTIAL_RESULT, + ORIGIN_RESULT, + HEAD_PARTIAL_RESULT, + }; +} + +async function testForPlatform(tab, monitor, testData) { + // GET request, no cookies (first request) + await performRequest("GET"); + for (const test of testData) { + await testClipboardContent(test.menuItemId, [ + ...test.data.SIMPLE_BASE, + ...test.data.BASE_RESULT, + ]); + } + // Check to make sure it is still OK after we view the response (bug#1452442) + await selectIndexAndWaitForSourceEditor(monitor, 0); + for (const test of testData) { + await testClipboardContent(test.menuItemId, [ + ...test.data.SIMPLE_BASE, + ...test.data.BASE_RESULT, + ]); + } + + // GET request, cookies set by previous response + await performRequest("GET"); + for (const test of testData) { + await testClipboardContent(test.menuItemId, [ + ...test.data.SIMPLE_BASE, + ...test.data.BASE_RESULT, + ...test.data.COOKIE_PARTIAL_RESULT, + ]); + } + + // Unfinished request (bug#1378464, bug#1420513) + const waitSlow = waitForNetworkEvents(monitor, 0); + await SpecialPowers.spawn( + tab.linkedBrowser, + [HTTPS_SLOW_SJS], + async function (url) { + content.wrappedJSObject.performRequest(url, "GET", null); + } + ); + await waitSlow; + for (const test of testData) { + await testClipboardContent(test.menuItemId, [ + ...test.data.SLOW_BASE, + ...test.data.BASE_RESULT, + ...test.data.COOKIE_PARTIAL_RESULT, + ]); + } + + // POST request + await performRequest("POST", POST_PAYLOAD); + for (const test of testData) { + await testClipboardContent(test.menuItemId, [ + ...test.data.SIMPLE_BASE, + ...test.data.BASE_RESULT, + ...test.data.COOKIE_PARTIAL_RESULT, + ...test.data.POST_PARTIAL_RESULT, + ...test.data.ORIGIN_RESULT, + ]); + } + + // HEAD request + await performRequest("HEAD"); + for (const test of testData) { + await testClipboardContent(test.menuItemId, [ + ...test.data.SIMPLE_BASE, + ...test.data.BASE_RESULT, + ...test.data.COOKIE_PARTIAL_RESULT, + ...test.data.HEAD_PARTIAL_RESULT, + ]); + } + + async function performRequest(method, payload) { + const waitRequest = waitForNetworkEvents(monitor, 1); + await SpecialPowers.spawn( + tab.linkedBrowser, + [ + { + url: HTTPS_SIMPLE_SJS, + method_: method, + payload_: payload, + }, + ], + async function ({ url, method_, payload_ }) { + content.wrappedJSObject.performRequest(url, method_, payload_); + } + ); + await waitRequest; + } + + async function testClipboardContent(menuItemId, expectedResult) { + const { document } = monitor.panelWin; + + const items = document.querySelectorAll(".request-list-item"); + const itemIndex = items.length - 1; + EventUtils.sendMouseEvent({ type: "mousedown" }, items[itemIndex]); + EventUtils.sendMouseEvent( + { type: "contextmenu" }, + document.querySelectorAll(".request-list-item")[0] + ); + + /* Ensure that the copy as cURL option is always visible */ + is( + !!getContextMenuItem(monitor, menuItemId), + true, + `The "Copy as cURL" context menu item "${menuItemId}" should not be hidden.` + ); + + await waitForClipboardPromise( + async function setup() { + await selectContextMenuItem(monitor, menuItemId); + }, + function validate(result) { + if (typeof result !== "string") { + return false; + } + + // Different setups may produce the same command, but with the + // parameters in a different order in the commandline (which is fine). + // Here we confirm that the commands are the same even in that case. + + // This monster regexp parses the command line into an array of arguments, + // recognizing quoted args with matching quotes and escaped quotes inside: + // [ "curl 'url'", "--standalone-arg", "-arg-with-quoted-string 'value\'s'" ] + const matchRe = /[-A-Za-z1-9]+(?: ([\"'])(?:\\\1|.)*?\1)?/g; + + const actual = result.match(matchRe); + // Must begin with the same "curl 'URL'" segment + if (!actual || expectedResult[0] != actual[0]) { + return false; + } + + // Must match each of the params in the middle (headers) + return ( + expectedResult.length === actual.length && + expectedResult.some(param => actual.includes(param)) + ); + } + ); + + info( + `Clipboard contains a cURL command for item ${itemIndex} by "${menuItemId}"` + ); + } +} |