From 0fffa9685fac4e28d840cff51a658c0b311dd358 Mon Sep 17 00:00:00 2001 From: bookmoons <35854232+bookmoons@users.noreply.github.com> Date: Thu, 27 Jun 2019 12:17:26 -0400 Subject: [PATCH] [k6] Restore real world usage tests (#36473) * k6: Rename JSON JSONValue Name JSON collides with the global JSON API. * k6: Restore real world usage tests --- types/k6/http.d.ts | 4 +- types/k6/index.d.ts | 12 +-- types/k6/k6-tests.ts | 219 +++++++++++++++++++++++++++++++++++++++++ types/k6/test/http.ts | 4 +- types/k6/tsconfig.json | 1 + 5 files changed, 227 insertions(+), 13 deletions(-) create mode 100644 types/k6/k6-tests.ts diff --git a/types/k6/http.d.ts b/types/k6/http.d.ts index f5e6917b87..70c74fc84b 100644 --- a/types/k6/http.d.ts +++ b/types/k6/http.d.ts @@ -1,4 +1,4 @@ -import { bytes, JSON } from '.'; +import { bytes, JSONValue } from '.'; import { Selection } from './html'; // Generics refine response to expose body with correct type @@ -137,7 +137,7 @@ export interface Response { params?: GenericParams | null; }): RefinedResponse; html(selector?: string): Selection; - json(selector?: string): JSON | undefined; + json(selector?: string): JSONValue | undefined; submitForm(args?: { formSelector?: string; fields?: { [name: string]: string }; diff --git a/types/k6/index.d.ts b/types/k6/index.d.ts index 4a57ac56a4..24b8a4ac36 100644 --- a/types/k6/index.d.ts +++ b/types/k6/index.d.ts @@ -37,14 +37,8 @@ export type byte = number; // [0,256) export type bytes = byte[]; // JavaScript value representable with JSON -export type JSON = - | null - | boolean - | number - | string - | JSONArray - | JSONObject; -export interface JSONArray extends Array {} +export type JSONValue = null | boolean | number | string | JSONArray | JSONObject; +export interface JSONArray extends Array {} export interface JSONObject { - [key: string]: JSON; + [key: string]: JSONValue; } diff --git a/types/k6/k6-tests.ts b/types/k6/k6-tests.ts new file mode 100644 index 0000000000..fdb6cb6736 --- /dev/null +++ b/types/k6/k6-tests.ts @@ -0,0 +1,219 @@ +import http from 'k6/http'; +import { JSONValue, JSONObject, check, sleep, fail, group } from 'k6'; + +const users = JSON.parse(open('./users.json')); +const __VU = 1; + +function test1() { + const user = users[__VU - 1]; + console.log(`${user.username}, ${user.password}`); + sleep(3); +} + +const binFile1 = open('/path/to/file.bin', 'b'); + +export default function test2() { + const data = { + field: 'this is a standard form field', + file: http.file(binFile1, 'test.bin'), + }; + const res = http.post('https://example.com/upload', data); + sleep(3); +} + +function test3() { + const res = http.get('http://httpbin.org', { responseType: 'text' }); + check(res, { + 'response code was 200': res => res.status === 200, + 'body size was 1234 bytes': res => res.body.length === 1234, + }); +} + +function test4() { + const res = http.get('https://loadimpact.com'); + check(res, { + 'status code MUST be 200': res => res.status === 200, + }) || fail('status code was *not* 200'); +} + +function test5() { + group('my user scenario', () => { + group('front page', () => { + const res = http.get('https://loadimpact.com'); + check(res, { + 'status code is 200': res => res.status === 200, + }); + }); + group('features page', () => { + const res = http.get('https://loadimpact.com/features'); + check(res, { + 'status code is 200': res => res.status === 200, + 'h1 message is correct': res => + res + .html('h1') + .text() + .startsWith('Simple yet realistic load testing'), + }); + }); + }); +} + +function test6() { + http.get('https://loadimpact.com'); + sleep(Math.random() * 30); + http.get('https://loadimpact.com/features'); +} + +function httpTest1() { + const responses = http.batch([ + 'http://test.loadimpact.com', + 'http://test.loadimpact.com/style.css', + 'http://test.loadimpact.com/images/logo.png', + ]); + check(responses[0], { + 'main page status was 200': res => res.status === 200, + }); +} + +function httpTest2() { + const req1: http.BatchRequest = { + method: 'GET', + url: 'http://httpbin.org/get', + params: { responseType: 'text' }, + }; + const req2: http.BatchRequest = { + method: 'GET', + url: 'http://test.loadimpact.com', + params: { responseType: 'text' }, + }; + const req3: http.BatchRequest = { + method: 'POST', + url: 'http://httpbin.org/post', + body: { hello: 'world!' }, + params: { + responseType: 'text', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + }, + }; + const responses: Array> = http.batch([req1, req2, req3]); + // httpbin.org should return our POST data in the response body, so + // we check the third response object to see that the POST worked. + check(responses[3], { + 'form data OK': res => JSON.parse(res.body)['form']['hello'] === 'world!', + }); +} + +const binFile = open('/path/to/file.bin', 'b'); + +function httpTest3() { + const data = { + field: 'this is a standard form field', + file: http.file(binFile, 'test.bin'), + }; + const res = http.post('https://example.com/upload', data); + sleep(3); +} + +function httpTest4() { + return http.get('https://loadimpact.com'); +} + +function httpTest5() { + const options = { maxRedirects: 10 }; + + const baseURL = 'https://dev-li-david.pantheonsite.io'; + + // Fetch the login page, with the login HTML form + const res1 = http.get(baseURL + '/user/login', { responseType: 'text' }); + + // Extract hidden value needed to POST form + const formBuildID = (res1.body.match('name="form_build_id" value="(.*)"') || [])[1]; + // Create an Object containing the form data + const formdata = { + name: 'testuser1', + pass: 'testuser1', + form_build_id: formBuildID, + form_id: 'user_login', + op: 'Log in', + }; + const headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + // Send login request + const res2 = http.post(baseURL + '/user/login', formdata, { headers }); + // Verify that we ended up on the user page + check(res2, { + 'login succeeded': res2 => res2.url === `${baseURL}/users/testuser1`, + }) || fail('login failed'); +} + +function httpTest6() { + const params = { + cookies: { my_cookie: 'value' }, + headers: { 'X-MyHeader': 'k6test' }, + redirects: 5, + tags: { k6test: 'yes' }, + }; + http.get('https://loadimpact.com', params); +} + +function httpTest7() { + const url1 = 'https://api.loadimpact.com/v3/account/me'; + const url2 = 'http://httpbin.org/get'; + const apiToken = 'f232831bda15dd233c53b9c548732c0197619a3d3c451134d9abded7eb5bb195'; + const requestHeaders = { + 'User-Agent': 'k6', + Authorization: 'Token ' + apiToken, + }; + + http.batch([{ method: 'GET', url: url1, params: { headers: requestHeaders } }, { method: 'GET', url: url2 }]); +} + +function jsonObject(value: JSONValue | undefined): value is JSONObject { + return !!value && typeof value === 'object' && !Array.isArray(value); +} + +function httpTest8() { + // Passing username and password as part of URL plus the auth option will authenticate using HTTP Digest authentication + const res = http.get('http://user:passwd@httpbin.org/digest-auth/auth/user/passwd', { auth: 'digest' }); + + // Verify response + check(res, { + 'status is 200': r => r.status === 200, + 'is authenticated': r => { + const json = r.json(); + return jsonObject(json) && !!json.authenticated; + }, + 'is correct user': r => { + const json = r.json(); + return jsonObject(json) && json.user === 'user'; + }, + }); +} + +function httpTest9() { + const res = http.get('https://loadimpact.com'); + for (const p in res.headers) { + if (res.headers.hasOwnProperty(p)) { + console.log(`${p} : ${res.headers[p]}`); + } + } + check(res, { + 'status is 200': r => r.status === 200, + 'caption is correct': r => r.html('h1').text() === 'Example Domain', + }); +} + +function httpTest10() { + // Request page with links + let res = http.get('https://httpbin.org/links/10/0'); + + // Now, click the 4th link on the page + res = res.clickLink({ selector: 'a:nth-child(4)' }); +} + +function httpTest11() { + // Request page containing a form + let res = http.get('https://httpbin.org/forms/post'); + + // Now, submit form setting/overriding some fields of the form + res = res.submitForm({ fields: { custname: 'test', extradata: 'test2' }, submitSelector: 'mySubmit' }); +} diff --git a/types/k6/test/http.ts b/types/k6/test/http.ts index 7d317128ed..47f4df9df5 100644 --- a/types/k6/test/http.ts +++ b/types/k6/test/http.ts @@ -1,4 +1,4 @@ -import { JSON } from 'k6'; +import { JSONValue } from 'k6'; import { Selection } from 'k6/html'; import { CookieJar, @@ -38,7 +38,7 @@ let responsesMapNone: { [name: string]: RefinedResponse<'none'> }; let responsesMapText: { [name: string]: RefinedResponse<'text'> }; let fileData: FileData; let html: Selection; -let json: JSON | undefined; +let json: JSONValue | undefined; let cookies: CookieJarCookies; let jar: CookieJar; diff --git a/types/k6/tsconfig.json b/types/k6/tsconfig.json index 3f7a48d09c..bf96c92334 100644 --- a/types/k6/tsconfig.json +++ b/types/k6/tsconfig.json @@ -23,6 +23,7 @@ "metrics.d.ts", "options.d.ts", "ws.d.ts", + "k6-tests.ts", "test/crypto.ts", "test/encoding.ts", "test/global.ts",