diff --git a/backend/controller/ingress/request.go b/backend/controller/ingress/request.go index d67031147c..99379dd475 100644 --- a/backend/controller/ingress/request.go +++ b/backend/controller/ingress/request.go @@ -173,7 +173,7 @@ func manglePathParameters(params map[string]string, ref *schema.Ref, sch *schema return map[string]any{}, nil } -// Takes the map of path parameters and transforms them into the appropriate type +// Takes the map of query parameters and transforms them into the appropriate type func mangleQueryParameters(params map[string]any, underlying map[string][]string, ref *schema.Ref, sch *schema.Schema) (any, error) { paramsField, err := getField("query", ref, sch) diff --git a/frontend/console/e2e/echo.spec.ts b/frontend/console/e2e/echo.spec.ts new file mode 100644 index 0000000000..5f26658458 --- /dev/null +++ b/frontend/console/e2e/echo.spec.ts @@ -0,0 +1,38 @@ +import { expect, ftlTest } from './ftl-test' +import { navigateToDecl } from './helpers' + +ftlTest('shows echo verb form', async ({ page }) => { + await navigateToDecl(page, 'echo', 'echo') + + await expect(page.getByText('CALL', { exact: true })).toBeVisible() + await expect(page.locator('input#request-path')).toHaveValue('echo.echo') + + await expect(page.getByText('Body', { exact: true })).toBeVisible() + await expect(page.getByText('Verb Schema', { exact: true })).toBeVisible() + await expect(page.getByText('JSONSchema', { exact: true })).toBeVisible() +}) + +ftlTest('send echo request', async ({ page }) => { + await navigateToDecl(page, 'echo', 'echo') + + // Check the initial value of the path input + const pathInput = page.locator('#request-path') + await expect(pathInput).toBeVisible() + const currentValue = await pathInput.inputValue() + expect(currentValue).toBe('echo.echo') + + const bodyEditor = page.locator('#body-editor .cm-content[contenteditable="true"]') + await expect(bodyEditor).toBeVisible() + await bodyEditor.fill('{\n "name": "wicket"\n}') + + await page.getByRole('button', { name: 'Send' }).click() + + const responseEditor = page.locator('#response-editor .cm-content[role="textbox"]') + await expect(responseEditor).toBeVisible() + + const responseText = await responseEditor.textContent() + const responseJson = JSON.parse(responseText.trim()) + + const expectedStart = 'Hello, wicket!!! It is ' + expect(responseJson.message.startsWith(expectedStart)).toBe(true) +}) diff --git a/frontend/console/e2e/helpers.ts b/frontend/console/e2e/helpers.ts new file mode 100644 index 0000000000..b9cc3305b3 --- /dev/null +++ b/frontend/console/e2e/helpers.ts @@ -0,0 +1,20 @@ +import { type Page, expect } from '@playwright/test' + +export async function navigateToModule(page: Page, moduleName: string) { + await page.getByRole('link', { name: 'Modules' }).click() + + // Navigate to the module page + await page.locator(`#module-${moduleName}-view-icon`).click() + await expect(page).toHaveURL(new RegExp(`/modules/${moduleName}`)) + + // Expand the module tree group + await page.locator(`#module-${moduleName}-tree-group`).click() +} + +export async function navigateToDecl(page: Page, moduleName: string, declName: string) { + await navigateToModule(page, moduleName) + + // Navigate to the decl page + await page.locator(`div#decl-${declName}`).click() + await expect(page).toHaveURL(new RegExp(`/modules/${moduleName}/verb/${declName}`)) +} diff --git a/frontend/console/e2e/ingress-http.spec.ts b/frontend/console/e2e/ingress-http.spec.ts new file mode 100644 index 0000000000..0a2358ac47 --- /dev/null +++ b/frontend/console/e2e/ingress-http.spec.ts @@ -0,0 +1,65 @@ +import { expect, ftlTest } from './ftl-test' +import { navigateToDecl } from './helpers' + +ftlTest('shows http ingress form', async ({ page }) => { + await navigateToDecl(page, 'http', 'get') + + await expect(page.locator('#call-type')).toHaveText('GET') + await expect(page.locator('input#request-path')).toHaveValue('http://localhost:8891/get/name') + + await expect(page.getByText('Body', { exact: true })).toBeVisible() + await expect(page.getByText('Verb Schema', { exact: true })).toBeVisible() + await expect(page.getByText('JSONSchema', { exact: true })).toBeVisible() +}) + +ftlTest('send get request with path and query params', async ({ page }) => { + await navigateToDecl(page, 'http', 'get') + + // Check the initial value of the path input + const pathInput = page.locator('#request-path') + await expect(pathInput).toBeVisible() + const currentValue = await pathInput.inputValue() + expect(currentValue).toBe('http://localhost:8891/get/name') + + // Update the path input to test path and query params + await pathInput.fill('http://localhost:8891/get/wicket?age=10') + await page.getByRole('button', { name: 'Send' }).click() + + const responseEditor = page.locator('#response-editor .cm-content[role="textbox"]') + await expect(responseEditor).toBeVisible() + + const responseText = await responseEditor.textContent() + const responseJson = JSON.parse(responseText) + + expect(responseJson).toEqual({ + age: '10', + name: 'wicket', + }) +}) + +ftlTest('send post request with body', async ({ page }) => { + await navigateToDecl(page, 'http', 'post') + + // Check the initial value of the path input + const pathInput = page.locator('#request-path') + await expect(pathInput).toBeVisible() + const currentValue = await pathInput.inputValue() + expect(currentValue).toBe('http://localhost:8891/post') + + const bodyEditor = page.locator('#body-editor .cm-content[contenteditable="true"]') + await expect(bodyEditor).toBeVisible() + await bodyEditor.fill('{\n "age": 10,\n "name": "wicket"\n}') + + await page.getByRole('button', { name: 'Send' }).click() + + const responseEditor = page.locator('#response-editor .cm-content[role="textbox"]') + await expect(responseEditor).toBeVisible() + + const responseText = await responseEditor.textContent() + const responseJson = JSON.parse(responseText) + + expect(responseJson).toEqual({ + age: 10, + name: 'wicket', + }) +}) diff --git a/frontend/console/e2e/module.spec.ts b/frontend/console/e2e/module.spec.ts index 519714efdc..ce78969070 100644 --- a/frontend/console/e2e/module.spec.ts +++ b/frontend/console/e2e/module.spec.ts @@ -1,14 +1,8 @@ import { expect, ftlTest } from './ftl-test' +import { navigateToModule } from './helpers' ftlTest('shows verbs for deployment', async ({ page }) => { - const modulesNavItem = page.getByRole('link', { name: 'Modules' }) - await modulesNavItem.click() - - const moduleEchoRow = page.locator('div.cursor-pointer').getByText('echo') - const moduleEcho = moduleEchoRow.locator('svg').nth(1) - await moduleEcho.click() - - await expect(page).toHaveURL(/\/modules\/echo/) + await navigateToModule(page, 'echo') await expect(page.getByText('module echo {')).toBeVisible() }) diff --git a/frontend/console/e2e/verb.spec.ts b/frontend/console/e2e/verb.spec.ts deleted file mode 100644 index 0b07462015..0000000000 --- a/frontend/console/e2e/verb.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { expect, ftlTest } from './ftl-test' - -ftlTest.beforeEach(async ({ page }) => { - const modulesNavItem = page.getByRole('link', { name: 'Modules' }) - await modulesNavItem.click() - - const moduleEcho = page.locator('div.cursor-pointer').getByText('echo').nth(0) - await moduleEcho.click() - - const verbEcho = page.locator('div#decl-echo') - await verbEcho.click() - - await expect(page).toHaveURL(/\/modules\/echo\/verb\/echo/) -}) - -ftlTest('shows verb form', async ({ page }) => { - await expect(page.getByText('CALL', { exact: true })).toBeVisible() - await expect(page.locator('input#request-path')).toHaveValue('echo.echo') - - await expect(page.getByText('Body', { exact: true })).toBeVisible() - await expect(page.getByText('Verb Schema', { exact: true })).toBeVisible() - await expect(page.getByText('JSONSchema', { exact: true })).toBeVisible() -}) diff --git a/frontend/console/src/components/CodeEditor.tsx b/frontend/console/src/components/CodeEditor.tsx index 7012e9590c..740153d90f 100644 --- a/frontend/console/src/components/CodeEditor.tsx +++ b/frontend/console/src/components/CodeEditor.tsx @@ -31,11 +31,13 @@ export const CodeEditor = ({ onTextChanged, readonly = false, schema, + id, }: { value: string onTextChanged?: (text: string) => void readonly?: boolean schema?: string + id?: string }) => { const { isDarkMode } = useUserPreferences() const editorContainerRef = useRef(null) @@ -111,5 +113,5 @@ export const CodeEditor = ({ } }, [value]) - return
+ return
} diff --git a/frontend/console/src/features/modules/ModulesTree.tsx b/frontend/console/src/features/modules/ModulesTree.tsx index b3e7646125..b4d7f5694a 100644 --- a/frontend/console/src/features/modules/ModulesTree.tsx +++ b/frontend/console/src/features/modules/ModulesTree.tsx @@ -96,6 +96,7 @@ const ModuleSection = ({