diff --git a/src/codegen/codegen.test.ts b/src/codegen/codegen.test.ts index cdcf32de..02f06f44 100644 --- a/src/codegen/codegen.test.ts +++ b/src/codegen/codegen.test.ts @@ -157,11 +157,12 @@ describe('Code generation', () => { ).toBe(expectedResult.replace(/\s/g, '')) }) - it('should replace correlated values', async () => { + describe('Correlation', () => { const rules: TestRule[] = [ { type: 'correlation', id: '1', + enabled: true, extractor: { filter: { path: '/login' }, selector: { @@ -174,6 +175,7 @@ describe('Code generation', () => { { type: 'correlation', id: '1', + enabled: true, extractor: { filter: { path: '' }, selector: { @@ -185,7 +187,8 @@ describe('Code generation', () => { }, ] - const expectedResult = await prettify(` + it('should replace correlated values', async () => { + const expectedResult = await prettify(` let params let resp let match @@ -236,9 +239,62 @@ describe('Code generation', () => { sleep(1) `) - expect( - await prettify(generateVUCode(correlationRecording, rules, thinkTime)) - ).toBe(expectedResult) + expect( + await prettify(generateVUCode(correlationRecording, rules, thinkTime)) + ).toBe(expectedResult) + }) + + it('should not generate correlation if rule is disabled', async () => { + const expectedResult = await prettify(` + let params + let resp + let match + let regex + let url + const correlation_vars = {} + + group('one', function () { + params = { + headers: {}, cookies: {} + } + + url = http.url\`http://test.k6.io/api/v1/foo\` + resp = http.request('POST', url, null, params) + + params = { + headers: {}, cookies: {} + } + + url = http.url\`http://test.k6.io/api/v1/login?project_id=555\` + resp = http.request('POST', url, null, params) + }) + + group('two', function () { + params = { + headers: {}, cookies: {} + } + + url = http.url\`http://test.k6.io/api/v1/users/333\` + resp = http.request('GET', url, null, params) + + params = { + headers: {}, cookies: {} + } + + url = http.url\`http://test.k6.io/api/v1/users\` + resp = http.request('POST', url, \`${JSON.stringify({ user_id: '333' })}\`, params) + }) + + sleep(1) + `) + + const disabledRules = rules.map((rule) => ({ ...rule, enabled: false })) + expect( + await prettify( + generateVUCode(correlationRecording, disabledRules, thinkTime) + ) + ).toBe(expectedResult) + }) }) it('should generate checks', () => { @@ -246,6 +302,7 @@ describe('Code generation', () => { { type: 'verification', id: '1', + enabled: true, filter: { path: '' }, value: { type: 'recordedValue', @@ -258,7 +315,6 @@ describe('Code generation', () => { url = http.url\`http://test.k6.io/api/v1/foo\` resp = http.request('POST', url, null, params) check(resp,{'statusmatches200':(r)=>r.status===200,}) - ` expect( @@ -270,7 +326,7 @@ describe('Code generation', () => { ).toBe(expectedResult.replace(/\s/g, '')) }) - it('should replace paremeterization values', async () => { + describe('Parameterization', () => { const recording = [ createProxyData({ id: '1', @@ -297,64 +353,119 @@ describe('Code generation', () => { }), ] - const rules: TestRule[] = [ - jsonRule, - customCodeReplaceProjectId, - customCodeReplaceCsrf, - ] - - const expectedResult = await prettify(` - let params - let resp - let match - let regex - let url - const correlation_vars = {} - - group('Default group', function () { - params = { - headers: { - 'content-type': \`application/json\` - }, - cookies: {} - } - - url = http.url\`http://test.k6.io/api/v1/users\` - resp = http.request('POST', url, \`${JSON.stringify({ user_id: 'TEST_ID' })}\`, params) - - params = { - headers: {}, - cookies: {}, - } - - function getParameterizationValue1() { - const randomInteger = Math.floor(Math.random() * 100000) - return randomInteger - } - function getParameterizationValue2() { - return '123456' - } - - url = http.url\`http://example.com/api/v1/users?project_id=\${getParameterizationValue1()}&csrf=\${getParameterizationValue2()}\` - resp = http.request('GET', url, null, params) - - - params = { - headers: {}, - cookies: {}, - } - - url = http.url\`http://example.com/api/v1/users?project_id=\${getParameterizationValue1()}\` - resp = http.request('GET', url, null, params) - }) - - sleep(1) - - `) - - expect(await prettify(generateVUCode(recording, rules, thinkTime))).toBe( - expectedResult - ) + it('should replace paremeterization values', async () => { + const rules: TestRule[] = [ + jsonRule, + customCodeReplaceProjectId, + customCodeReplaceCsrf, + ] + + const expectedResult = await prettify(` + let params + let resp + let match + let regex + let url + const correlation_vars = {} + + group('Default group', function () { + params = { + headers: { + 'content-type': \`application/json\` + }, + cookies: {} + } + + url = http.url\`http://test.k6.io/api/v1/users\` + resp = http.request('POST', url, \`${JSON.stringify({ user_id: 'TEST_ID' })}\`, params) + + params = { + headers: {}, + cookies: {}, + } + + function getParameterizationValue1() { + const randomInteger = Math.floor(Math.random() * 100000) + return randomInteger + } + function getParameterizationValue2() { + return '123456' + } + + url = http.url\`http://example.com/api/v1/users?project_id=\${getParameterizationValue1()}&csrf=\${getParameterizationValue2()}\` + resp = http.request('GET', url, null, params) + + + params = { + headers: {}, + cookies: {}, + } + + url = http.url\`http://example.com/api/v1/users?project_id=\${getParameterizationValue1()}\` + resp = http.request('GET', url, null, params) + }) + + sleep(1) + + `) + + expect( + await prettify(generateVUCode(recording, rules, thinkTime)) + ).toBe(expectedResult) + }) + + it('should not replace paremeterization values if rule is disabled', async () => { + const disabledRules: TestRule[] = [ + { ...jsonRule, enabled: false }, + { ...customCodeReplaceProjectId, enabled: false }, + { ...customCodeReplaceCsrf, enabled: false }, + ] + + const expectedResult = await prettify(` + let params + let resp + let match + let regex + let url + const correlation_vars = {} + + group('Default group', function () { + params = { + headers: { + 'content-type': \`application/json\` + }, + cookies: {} + } + + url = http.url\`http://test.k6.io/api/v1/users\` + resp = http.request('POST', url, \`${JSON.stringify({ user_id: '333' })}\`, params) + + params = { + headers: {}, + cookies: {}, + } + + url = http.url\`http://example.com/api/v1/users?project_id=123&csrf=321\` + resp = http.request('GET', url, null, params) + + + params = { + headers: {}, + cookies: {}, + } + + url = http.url\`http://example.com/api/v1/users?project_id=123\` + resp = http.request('GET', url, null, params) + }) + + sleep(1) + + `) + + expect( + await prettify(generateVUCode(recording, disabledRules, thinkTime)) + ).toBe(expectedResult) + }) }) }) diff --git a/src/rules/correlation.ts b/src/rules/correlation.ts index 7f5b36e2..e4e116a7 100644 --- a/src/rules/correlation.ts +++ b/src/rules/correlation.ts @@ -859,6 +859,7 @@ correlation_vars['correlation_1'] = resp.json().user_id` const rule: CorrelationRule = { type: 'correlation', id: '1', + enabled: true, extractor: { filter: { path: '' }, selector: { @@ -906,6 +907,7 @@ correlation_vars['correlation_1'] = resp.json().user_id` const rule: CorrelationRule = { type: 'correlation', id: '1', + enabled: true, extractor: { filter: { path: '' }, selector: { @@ -960,6 +962,7 @@ correlation_vars['correlation_1'] = resp.json().user_id` const rule: CorrelationRule = { type: 'correlation', id: '1', + enabled: true, extractor: { filter: { path: '' }, selector: { diff --git a/src/rules/rules.ts b/src/rules/rules.ts index 874e7ec4..aeebf0ea 100644 --- a/src/rules/rules.ts +++ b/src/rules/rules.ts @@ -20,9 +20,9 @@ function createSequentialIdPool() { export function applyRules(recording: ProxyData[], rules: TestRule[]) { const idGenerator = createSequentialIdPool() - const ruleInstances = rules.map((rule) => - createRuleInstance(rule, idGenerator(rule.type)) - ) + const ruleInstances = rules + .filter((rule) => rule.enabled) + .map((rule) => createRuleInstance(rule, idGenerator(rule.type))) const requestSnippetSchemas = recording.map((data) => ruleInstances.reduce((acc, rule) => rule.apply(acc), { diff --git a/src/schemas/rules.ts b/src/schemas/rules.ts index 2d7ede65..40c09eae 100644 --- a/src/schemas/rules.ts +++ b/src/schemas/rules.ts @@ -98,6 +98,7 @@ export const CorrelationReplacerSchema = z.object({ export const RuleBaseSchema = z.object({ id: z.string(), + enabled: z.boolean().default(true), }) export const ParameterizationRuleSchema = RuleBaseSchema.extend({ diff --git a/src/store/generator/fixtures.ts b/src/store/generator/fixtures.ts index 131d8cfb..964e9769 100644 --- a/src/store/generator/fixtures.ts +++ b/src/store/generator/fixtures.ts @@ -3,6 +3,7 @@ import { TestRule } from '@/types/rules' export const rules: TestRule[] = [ { id: '0', + enabled: true, type: 'customCode', filter: { path: '' }, snippet: 'console.log("Hello, world!")', @@ -11,6 +12,7 @@ export const rules: TestRule[] = [ { type: 'correlation', id: '1', + enabled: true, extractor: { filter: { path: '' }, selector: { @@ -24,6 +26,7 @@ export const rules: TestRule[] = [ { type: 'correlation', id: '3', + enabled: true, extractor: { filter: { path: '' }, selector: { @@ -36,6 +39,7 @@ export const rules: TestRule[] = [ { type: 'correlation', id: '2', + enabled: true, extractor: { filter: { path: '' }, selector: { @@ -49,6 +53,7 @@ export const rules: TestRule[] = [ { type: 'correlation', id: '4', + enabled: true, extractor: { filter: { path: 'api.k6.io/v3/account/me' }, selector: { diff --git a/src/store/generator/slices/rules.ts b/src/store/generator/slices/rules.ts index 60c97628..7e03148c 100644 --- a/src/store/generator/slices/rules.ts +++ b/src/store/generator/slices/rules.ts @@ -11,6 +11,7 @@ interface Actions { updateRule: (rule: TestRule) => void cloneRule: (id: string) => void deleteRule: (id: string) => void + toggleEnableRule: (id: string) => void swapRules: (idA: string, idB: string) => void setSelectedRuleId: (id: string | null) => void } @@ -47,6 +48,13 @@ export const createRulesSlice: ImmerStateCreator = (set) => ({ state.rules = state.rules.filter((rule) => rule.id !== id) }), + toggleEnableRule: (id) => + set((state) => { + const index = state.rules.findIndex((r) => r.id === id) + if (state.rules[index]) { + state.rules[index].enabled = !state.rules[index].enabled + } + }), swapRules: (idA, idB) => set((state) => { const indexA = state.rules.findIndex((rule) => rule.id === idA) diff --git a/src/test/factories/generator.ts b/src/test/factories/generator.ts index ae311317..63c566a7 100644 --- a/src/test/factories/generator.ts +++ b/src/test/factories/generator.ts @@ -39,6 +39,7 @@ export function createGeneratorState( addRule: vi.fn(), cloneRule: vi.fn(), deleteRule: vi.fn(), + toggleEnableRule: vi.fn(), rules: [], swapRules: vi.fn(), updateRule: vi.fn(), diff --git a/src/test/fixtures/parameterizationRules.ts b/src/test/fixtures/parameterizationRules.ts index ff69b180..ed2f919e 100644 --- a/src/test/fixtures/parameterizationRules.ts +++ b/src/test/fixtures/parameterizationRules.ts @@ -3,6 +3,7 @@ import { ParameterizationRule } from '@/types/rules' export const jsonRule = { type: 'parameterization', id: '1', + enabled: true, filter: { path: '' }, selector: { type: 'json', @@ -18,6 +19,7 @@ export const jsonRule = { export const urlRule = { type: 'parameterization', id: '2', + enabled: true, filter: { path: '' }, selector: { type: 'begin-end', @@ -34,6 +36,7 @@ export const urlRule = { export const headerRule = { type: 'parameterization', id: '3', + enabled: true, filter: { path: '' }, selector: { type: 'regex', @@ -49,6 +52,7 @@ export const headerRule = { export const customCodeReplaceProjectId = { type: 'parameterization', id: '4', + enabled: true, filter: { path: '' }, selector: { type: 'regex', @@ -67,6 +71,7 @@ export const customCodeReplaceProjectId = { export const customCodeReplaceCsrf = { type: 'parameterization', id: '4', + enabled: true, filter: { path: '' }, selector: { type: 'regex', diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 3d6911e3..0c5b2719 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -6,6 +6,7 @@ export function createEmptyRule(type: TestRule['type']): TestRule { return { type: 'correlation', id: self.crypto.randomUUID(), + enabled: true, extractor: { filter: { path: '' }, selector: { @@ -23,6 +24,7 @@ export function createEmptyRule(type: TestRule['type']): TestRule { return { type: 'customCode', id: self.crypto.randomUUID(), + enabled: true, filter: { path: '' }, snippet: '', placement: 'before', @@ -31,6 +33,7 @@ export function createEmptyRule(type: TestRule['type']): TestRule { return { type: 'parameterization', id: self.crypto.randomUUID(), + enabled: true, filter: { path: '' }, selector: { type: 'begin-end', @@ -44,6 +47,7 @@ export function createEmptyRule(type: TestRule['type']): TestRule { return { type: 'verification', id: self.crypto.randomUUID(), + enabled: true, filter: { path: '' }, selector: { type: 'begin-end', diff --git a/src/views/Generator/RuleEditor/RuleEditor.tsx b/src/views/Generator/RuleEditor/RuleEditor.tsx index 22c3bb2f..b754d947 100644 --- a/src/views/Generator/RuleEditor/RuleEditor.tsx +++ b/src/views/Generator/RuleEditor/RuleEditor.tsx @@ -40,6 +40,17 @@ export function RuleEditorSwitch() { } } +function RuleDisabledWarning() { + return ( + + + + + This rule is currently disabled. + + ) +} + interface RuleEditorProps { rule: TestRule } @@ -100,6 +111,7 @@ export function RuleEditor({ rule }: RuleEditorProps) { + {!rule.enabled && } diff --git a/src/views/Generator/TestRuleContainer/TestRule/TestRule.tsx b/src/views/Generator/TestRuleContainer/TestRule/TestRule.tsx index 0ca1fe6d..e202e9f0 100644 --- a/src/views/Generator/TestRuleContainer/TestRule/TestRule.tsx +++ b/src/views/Generator/TestRuleContainer/TestRule/TestRule.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/react' import { useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' -import { Flex, Grid, IconButton } from '@radix-ui/themes' +import { Flex, Grid, IconButton, Tooltip } from '@radix-ui/themes' import { DragHandleDots2Icon } from '@radix-ui/react-icons' import type { TestRule } from '@/types/rules' @@ -48,62 +48,66 @@ export function TestRuleItem({ : undefined return ( - + ) } diff --git a/src/views/Generator/TestRuleContainer/TestRule/TestRuleActions.tsx b/src/views/Generator/TestRuleContainer/TestRule/TestRuleActions.tsx index ddb4dfbd..7c6e4484 100644 --- a/src/views/Generator/TestRuleContainer/TestRule/TestRuleActions.tsx +++ b/src/views/Generator/TestRuleContainer/TestRule/TestRuleActions.tsx @@ -6,11 +6,13 @@ import { useGeneratorStore } from '@/store/generator' interface TestRuleActionsProps { ruleId: string + enabled: boolean } -export function TestRuleActions({ ruleId }: TestRuleActionsProps) { +export function TestRuleActions({ ruleId, enabled }: TestRuleActionsProps) { const cloneRule = useGeneratorStore((state) => state.cloneRule) const deleteRule = useGeneratorStore((state) => state.deleteRule) + const toggleEnableRule = useGeneratorStore((state) => state.toggleEnableRule) const setSelectedRuleId = useGeneratorStore( (state) => state.setSelectedRuleId ) @@ -27,6 +29,10 @@ export function TestRuleActions({ ruleId }: TestRuleActionsProps) { cloneRule(ruleId) } + const handleToggleEnabled = () => { + toggleEnableRule(ruleId) + } + return ( @@ -42,6 +48,9 @@ export function TestRuleActions({ ruleId }: TestRuleActionsProps) { + + {enabled ? 'Disable' : 'Enable'} + Edit Duplicate