From 1e387c5ffce30a3c052afb555c174bcef464797e Mon Sep 17 00:00:00 2001 From: John Kaster Date: Fri, 29 Oct 2021 00:45:05 +0000 Subject: [PATCH 1/4] WIP check for required properties in RunIt body param --- packages/run-it/src/RunIt.tsx | 6 ++- .../components/RequestForm/formUtils.spec.tsx | 39 +++++++++------- .../src/components/RequestForm/formUtils.tsx | 44 ++++++++++++++----- 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/packages/run-it/src/RunIt.tsx b/packages/run-it/src/RunIt.tsx index af5a07c6c..4d1c38dc7 100644 --- a/packages/run-it/src/RunIt.tsx +++ b/packages/run-it/src/RunIt.tsx @@ -163,10 +163,12 @@ export const RunIt: FC = ({ requestContent ) if (body) { - const message = validateBody(body) + const [bodyParam] = method.bodyParams + const requiredKeys = Object.keys(bodyParam.type.requiredProperties) + const message = validateBody(body, requiredKeys) setValidationMessage(message) if (message) { - // syntax error, don't run + // body has an error, don't run return } } diff --git a/packages/run-it/src/components/RequestForm/formUtils.spec.tsx b/packages/run-it/src/components/RequestForm/formUtils.spec.tsx index c41334bf1..8a6a4de32 100644 --- a/packages/run-it/src/components/RequestForm/formUtils.spec.tsx +++ b/packages/run-it/src/components/RequestForm/formUtils.spec.tsx @@ -270,27 +270,34 @@ describe('Complex Item', () => { }) describe('validateBody', () => { + const requiredKeys = ['model', 'view'] test.each` - value | error + value | expected | requiredKeys + ${{ + view: 'users', + fields: ['users.id', 'users.first_name'], +}} | ${'Error: Required properties "model" must be provided'} | ${requiredKeys} ${{ model: 'thelook', view: 'users', fields: ['users.id', 'users.first_name'], -}} | ${''} - ${'na.-_me=Vapor&age=3&luckyNumbers[]=5&luckyNumbers[]=7'} | ${''} - ${'name=Vapor&age=3&luckyNumbers[]=5&luckyNumbers[]7'} | ${'luckyNumbers[]7'} - ${'{'} | ${'Unexpected end of JSON input'} - ${'}'} | ${'Unexpected token } in JSON at position 0'} - ${'['} | ${'Unexpected end of JSON input'} - ${'"'} | ${'Unexpected end of JSON input'} - ${'"foo"'} | ${''} - ${''} | ${''} - ${'{}'} | ${''} - `('it validates a body value of "$value"', ({ value, error }) => { - const actual = validateBody(value) - const expected = error ? `Syntax error in the body: ${error}` : error - expect(actual).toEqual(expected) - }) +}} | ${''} | ${requiredKeys} + ${'na.-_me=Vapor&age=3&luckyNumbers[]=5&luckyNumbers[]=7'} | ${''} | ${[]} + ${'name=Vapor&age=3&luckyNumbers[]=5&luckyNumbers[]7'} | ${'Syntax error in the body: luckyNumbers[]7'} | ${[]} + ${'{'} | ${'Syntax error in the body: Unexpected end of JSON input'} | ${[]} + ${'}'} | ${'Syntax error in the body: Unexpected token } in JSON at position 0'} | ${[]} + ${'['} | ${'Syntax error in the body: Unexpected end of JSON input'} | ${[]} + ${'"'} | ${'Syntax error in the body: Unexpected end of JSON input'} | ${[]} + ${'"foo"'} | ${''} | ${[]} + ${''} | ${''} | ${[]} + ${'{}'} | ${''} | ${[]} + `( + 'it validates a body value of "$value"', + ({ value, expected, requiredKeys }) => { + const actual = validateBody(value, requiredKeys) + expect(actual).toEqual(expected) + } + ) }) }) diff --git a/packages/run-it/src/components/RequestForm/formUtils.tsx b/packages/run-it/src/components/RequestForm/formUtils.tsx index 396f57441..5179678e2 100644 --- a/packages/run-it/src/components/RequestForm/formUtils.tsx +++ b/packages/run-it/src/components/RequestForm/formUtils.tsx @@ -42,7 +42,6 @@ import { import { Info } from '@styled-icons/material' import { DateFormat, InputDate } from '@looker/components-date' import { CodeEditor } from '@looker/code-editor' - import type { RunItInput, RunItValues } from '../../RunIt' import { FormItem } from './FormItem' @@ -345,23 +344,44 @@ export const validateEncodedValues = (body: string) => { * Returns an error message if the body is not JSON or valid form url encoding * * @param body string to validate + * @param requiredKeys keys that are required in the body parameter */ -export const validateBody = (body: string | Record) => { +export const validateBody = ( + body: string | Record, + requiredKeys: string[] +) => { + let parsed + let result = '' - if (body && typeof body === 'string') { - if (/^[[{}"]/.test(body)) { - // most likely JSON - try { - JSON.parse(body) - } catch (e: any) { - result = e.message + if (body) { + if (typeof body === 'string') { + if (/^[[{}"]/.test(body)) { + // most likely JSON + try { + parsed = JSON.parse(body) + } catch (e: any) { + result = e.message + } + } else { + result = validateEncodedValues(body) + } + if (result) { + result = `Syntax error in the body: ${result}` } } else { - result = validateEncodedValues(body) + parsed = body } } - if (result) { - result = `Syntax error in the body: ${result}` + + if (parsed && requiredKeys && requiredKeys.length > 0) { + const required = new Set(requiredKeys) + const keys = new Set(Object.keys(parsed)) + const missing = new Set([...required].filter((k) => !keys.has(k))) + if (missing.size > 0) { + result = `Error: Required properties "${Array.from(missing).join( + ', ' + )}" must be provided` + } } return result } From 5e673a1b9bd2d35f807f34eca9285843ffd1cc3b Mon Sep 17 00:00:00 2001 From: John Kaster Date: Fri, 29 Oct 2021 17:58:18 +0000 Subject: [PATCH 2/4] added required property runit tests --- packages/run-it/src/RunIt.spec.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/run-it/src/RunIt.spec.tsx b/packages/run-it/src/RunIt.spec.tsx index 831386d7b..3efaa8baa 100644 --- a/packages/run-it/src/RunIt.spec.tsx +++ b/packages/run-it/src/RunIt.spec.tsx @@ -94,7 +94,7 @@ describe('RunIt', () => { }) test('the form submit handler invokes the request callback on submit', async () => { - renderRunIt() + renderRunIt(api, api.methods.me) const defaultRequestCallback = jest .spyOn(sdk.authSession.transport, 'rawRequest') .mockResolvedValueOnce(testTextResponse) @@ -108,6 +108,22 @@ describe('RunIt', () => { ).toBeInTheDocument() }) }) + + test('run_inline_query has required body parameters', async () => { + renderRunIt() + const defaultRequestCallback = jest + .spyOn(sdk.authSession.transport, 'rawRequest') + .mockResolvedValueOnce(testTextResponse) + const button = screen.getByRole('button', { name: run }) + expect(button).toBeInTheDocument() + userEvent.click(button) + await waitFor(() => { + expect(defaultRequestCallback).not.toHaveBeenCalled() + expect(screen.queryByRole('status')).toHaveTextContent( + 'Error: Required properties "model, view" must be provided' + ) + }) + }) }) describe('not configured or authenticated', () => { From d418ea339e4f359cfc6cb514300cfaac31eb7738 Mon Sep 17 00:00:00 2001 From: John Kaster Date: Fri, 29 Oct 2021 18:04:22 +0000 Subject: [PATCH 3/4] clarified error message --- packages/run-it/src/RunIt.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/run-it/src/RunIt.spec.tsx b/packages/run-it/src/RunIt.spec.tsx index 3efaa8baa..5c2490f73 100644 --- a/packages/run-it/src/RunIt.spec.tsx +++ b/packages/run-it/src/RunIt.spec.tsx @@ -120,7 +120,7 @@ describe('RunIt', () => { await waitFor(() => { expect(defaultRequestCallback).not.toHaveBeenCalled() expect(screen.queryByRole('status')).toHaveTextContent( - 'Error: Required properties "model, view" must be provided' + 'Error: Required properties "model, view" must be provided in the body' ) }) }) From e983fedcb81ca14dc49bc514ab2b7b289673562e Mon Sep 17 00:00:00 2001 From: John Kaster Date: Fri, 29 Oct 2021 18:05:19 +0000 Subject: [PATCH 4/4] missed some commits --- packages/run-it/src/components/RequestForm/formUtils.spec.tsx | 2 +- packages/run-it/src/components/RequestForm/formUtils.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/run-it/src/components/RequestForm/formUtils.spec.tsx b/packages/run-it/src/components/RequestForm/formUtils.spec.tsx index 8a6a4de32..4af6b7c20 100644 --- a/packages/run-it/src/components/RequestForm/formUtils.spec.tsx +++ b/packages/run-it/src/components/RequestForm/formUtils.spec.tsx @@ -276,7 +276,7 @@ describe('Complex Item', () => { ${{ view: 'users', fields: ['users.id', 'users.first_name'], -}} | ${'Error: Required properties "model" must be provided'} | ${requiredKeys} +}} | ${'Error: Required properties "model" must be provided in the body'} | ${requiredKeys} ${{ model: 'thelook', view: 'users', diff --git a/packages/run-it/src/components/RequestForm/formUtils.tsx b/packages/run-it/src/components/RequestForm/formUtils.tsx index 5179678e2..6a109cc78 100644 --- a/packages/run-it/src/components/RequestForm/formUtils.tsx +++ b/packages/run-it/src/components/RequestForm/formUtils.tsx @@ -380,7 +380,7 @@ export const validateBody = ( if (missing.size > 0) { result = `Error: Required properties "${Array.from(missing).join( ', ' - )}" must be provided` + )}" must be provided in the body` } } return result