-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: recording verification rule #128
Changes from all commits
c0acf1f
d7aecb6
fa58287
ce8f2eb
31af402
a4ac996
a8142e0
80f2241
df49153
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
export function handleSummary(data) { | ||
const checks = [] | ||
data.root_group.checks.forEach((check) => { | ||
checks.push(check) | ||
}) | ||
data.root_group.groups.forEach((group) => { | ||
group.checks.forEach((check) => { | ||
checks.push(check) | ||
}) | ||
}) | ||
return { | ||
stdout: JSON.stringify(checks), | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { RequestSnippetSchema } from '@/types' | ||
|
||
export function applyRecordingVerificationRule( | ||
requestSnippetSchema: RequestSnippetSchema | ||
): RequestSnippetSchema { | ||
const response = requestSnippetSchema.data.response | ||
|
||
if (!response) { | ||
return requestSnippetSchema | ||
} | ||
|
||
const verificationSnippet = ` | ||
check(resp, { | ||
'Recording Verification Rule: status matches recording': (r) => r.status === ${response.statusCode}, | ||
}) | ||
` | ||
return { | ||
...requestSnippetSchema, | ||
after: [...requestSnippetSchema['after'], verificationSnippet], | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ import { spawn, ChildProcessWithoutNullStreams } from 'node:child_process' | |
import { readFile, writeFile } from 'fs/promises' | ||
import path from 'path' | ||
import readline from 'readline/promises' | ||
import { K6Log } from './types' | ||
import { K6Check, K6Log } from './types' | ||
import { getArch, getPlatform } from './utils/electron' | ||
|
||
export type K6Process = ChildProcessWithoutNullStreams | ||
|
@@ -65,7 +65,6 @@ export const runScript = async ( | |
'--iterations=1', | ||
'--insecure-skip-tls-verify', | ||
'--log-format=json', | ||
'--no-summary', | ||
'--quiet', | ||
], | ||
{ | ||
|
@@ -75,14 +74,16 @@ export const runScript = async ( | |
|
||
// we use a reader to read entire lines from stderr instead of buffered data | ||
const stderrReader = readline.createInterface(k6.stderr) | ||
const stdoutReader = readline.createInterface(k6.stdout) | ||
|
||
k6.stdout.on('data', (data) => { | ||
console.error(`stdout: ${data}`) | ||
stdoutReader.on('line', (data) => { | ||
console.log(`stdout: ${data}`) | ||
|
||
const checkData: K6Check[] = JSON.parse(data) | ||
browserWindow.webContents.send('script:check', checkData) | ||
}) | ||
|
||
stderrReader.on('line', (data) => { | ||
console.log(`stderr: ${data}`) | ||
|
||
const logData: K6Log = JSON.parse(data) | ||
browserWindow.webContents.send('script:log', logData) | ||
}) | ||
|
@@ -96,12 +97,25 @@ export const runScript = async ( | |
} | ||
|
||
const enhanceScript = async (scriptPath: string) => { | ||
const groupSnippet = await getGroupSnippet() | ||
const groupSnippet = await getJsSnippet('group_snippet.js') | ||
const checksSnippet = await getJsSnippet('checks_snippet.js') | ||
const scriptContent = await readFile(scriptPath, { encoding: 'utf-8' }) | ||
const scriptLines = scriptContent.split('\n') | ||
const httpImportIndex = scriptLines.findIndex((line) => | ||
line.includes('k6/http') | ||
) | ||
const handleSummaryIndex = scriptLines.findIndex( | ||
(line) => | ||
// NOTE: if the custom handle summary is commented out we can still insert our snippet | ||
// this check should be improved | ||
line.includes('export function handleSummary(') && !line.includes('//') | ||
) | ||
|
||
// NOTE: checks works only if the user doesn't define a custom summary handler | ||
// if no custom handleSummary is defined we add our version to retrieve checks | ||
if (handleSummaryIndex === -1) { | ||
scriptLines.push(checksSnippet) | ||
} | ||
Comment on lines
+107
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only add the checks retrieval logic via |
||
|
||
if (httpImportIndex !== -1) { | ||
scriptLines.splice(httpImportIndex + 1, 0, groupSnippet) | ||
|
@@ -112,19 +126,15 @@ const enhanceScript = async (scriptPath: string) => { | |
} | ||
} | ||
|
||
const getGroupSnippet = async () => { | ||
let groupSnippetPath: string | ||
const getJsSnippet = async (snippetName: string) => { | ||
let jsSnippetPath: string | ||
|
||
// if we are in dev server we take resources directly, otherwise look in the app resources folder. | ||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { | ||
groupSnippetPath = path.join( | ||
app.getAppPath(), | ||
'resources', | ||
'group_snippet.js' | ||
) | ||
jsSnippetPath = path.join(app.getAppPath(), 'resources', snippetName) | ||
} else { | ||
groupSnippetPath = path.join(process.resourcesPath, 'group_snippet.js') | ||
jsSnippetPath = path.join(process.resourcesPath, snippetName) | ||
} | ||
|
||
return readFile(groupSnippetPath, { encoding: 'utf-8' }) | ||
return readFile(jsSnippetPath, { encoding: 'utf-8' }) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { ProxyData } from '@/types' | ||
|
||
export const checksRecording: ProxyData[] = [ | ||
{ | ||
id: '1', | ||
request: { | ||
method: 'POST', | ||
url: 'http://test.k6.io/api/v1/foo', | ||
headers: [], | ||
cookies: [], | ||
query: [], | ||
scheme: 'http', | ||
host: 'localhost:3000', | ||
content: '', | ||
path: '/api/v1/foo', | ||
timestampStart: 0, | ||
timestampEnd: 0, | ||
contentLength: 0, | ||
httpVersion: '1.1', | ||
}, | ||
response: { | ||
statusCode: 200, | ||
path: '/api/v1/foo', | ||
reason: 'OK', | ||
httpVersion: '1.1', | ||
headers: [['Content-Type', 'application/json']], | ||
cookies: [], | ||
content: JSON.stringify({ user_id: '444' }), | ||
contentLength: 0, | ||
timestampStart: 0, | ||
}, | ||
}, | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import { GeneratorFileData } from '@/types/generator' | ||
import { RampingStage } from '@/types/testOptions' | ||
import { createEmptyRule } from './rules' | ||
|
||
export function createNewGeneratorFile(recordingPath = ''): GeneratorFileData { | ||
return { | ||
|
@@ -21,7 +22,7 @@ export function createNewGeneratorFile(recordingPath = ''): GeneratorFileData { | |
testData: { | ||
variables: [], | ||
}, | ||
rules: [], | ||
rules: [createEmptyRule('recording-verification')], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this rule will be present on every newly created generator as a default that can be removed |
||
allowlist: [], | ||
includeStaticAssets: false, | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The checks are now grouped under a single name.
This still respects groups differences, in case you have two groups, it will be two checks