diff --git a/zeppelin-web-angular/e2e/models/interpreter-repository-modal.ts b/zeppelin-web-angular/e2e/models/interpreter-repository-modal.ts new file mode 100644 index 00000000000..c4fc0d17827 --- /dev/null +++ b/zeppelin-web-angular/e2e/models/interpreter-repository-modal.ts @@ -0,0 +1,65 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Locator, Page } from '@playwright/test'; +import { BasePage } from './base-page'; + +export interface RepositoryProxyCredentials { + id: string; + url: string; + proxyLogin: string; + proxyPassword: string; +} + +export class InterpreterRepositoryModal extends BasePage { + readonly repositoryTrigger: Locator; + readonly createRepositoryTag: Locator; + readonly idInput: Locator; + readonly urlInput: Locator; + readonly proxyLoginInput: Locator; + readonly proxyPasswordInput: Locator; + readonly addButton: Locator; + + constructor(page: Page) { + super(page); + this.repositoryTrigger = page.locator('button.repository-trigger'); + this.createRepositoryTag = page.locator('nz-tag.editable-tag'); + this.idInput = page.locator('input[placeholder="Repository id"]'); + this.urlInput = page.locator('input[placeholder="Repository url"]'); + // Locate the proxy inputs by placeholder, not formControlName: the binding is + // what the regression test exercises, so the locator must not depend on it. + this.proxyLoginInput = page.locator('input[placeholder="proxy login"]'); + this.proxyPasswordInput = page.locator('input[placeholder="proxy password"]'); + this.addButton = page.getByRole('button', { name: 'Add', exact: true }); + } + + async navigate(): Promise { + await this.navigateToRoute('/interpreter'); + } + + async openCreateModal(): Promise { + await this.repositoryTrigger.click(); + await this.createRepositoryTag.click(); + await this.idInput.waitFor({ state: 'visible' }); + } + + async fillProxyRepository(form: RepositoryProxyCredentials): Promise { + await this.fillAndVerifyInput(this.idInput, form.id); + await this.fillAndVerifyInput(this.urlInput, form.url); + await this.fillAndVerifyInput(this.proxyLoginInput, form.proxyLogin); + await this.fillAndVerifyInput(this.proxyPasswordInput, form.proxyPassword); + } + + async submit(): Promise { + await this.addButton.click(); + } +} diff --git a/zeppelin-web-angular/e2e/tests/workspace/interpreter/create-repository-proxy-credentials.spec.ts b/zeppelin-web-angular/e2e/tests/workspace/interpreter/create-repository-proxy-credentials.spec.ts new file mode 100644 index 00000000000..ec156dac60b --- /dev/null +++ b/zeppelin-web-angular/e2e/tests/workspace/interpreter/create-repository-proxy-credentials.spec.ts @@ -0,0 +1,66 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect, test } from '@playwright/test'; +import { InterpreterRepositoryModal } from '../../../models/interpreter-repository-modal'; +import { addPageAnnotationBeforeEach, waitForZeppelinReady, PAGES } from '../../../utils'; + +interface AddRepositoryRequestBody { + id: string; + proxyLogin?: string; + proxyPassword?: string; +} + +test.describe('Interpreter Repository - Proxy Credentials', () => { + addPageAnnotationBeforeEach(PAGES.WORKSPACE.INTERPRETER_CREATE_REPO); + + test('submits the proxy login and proxy password to their own fields', async ({ page }) => { + const proxyLogin = 'proxy-user'; + const proxyPassword = 'proxy-secret'; + const repoId = `e2e-proxy-repo-${Date.now()}`; + + await page.goto('/#/interpreter'); + await waitForZeppelinReady(page); + + // Intercept the POST so the payload can be checked without persisting a repo. + let requestBody: AddRepositoryRequestBody | null = null; + await page.route('**/api/interpreter/repository', async route => { + if (route.request().method() === 'POST') { + requestBody = route.request().postDataJSON() as AddRepositoryRequestBody; + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ status: 'OK', message: '', body: '' }) + }); + return; + } + await route.continue(); + }); + + const modal = new InterpreterRepositoryModal(page); + await modal.openCreateModal(); + await modal.fillProxyRepository({ + id: repoId, + url: 'repo1.maven.org/maven2/', + proxyLogin, + proxyPassword + }); + await modal.submit(); + + await expect.poll(() => requestBody, { timeout: 15000 }).not.toBeNull(); + + // Regression guard for ZEPPELIN-6520: Password was bound to proxyLogin, so the + // password overwrote the login and proxyPassword was always empty. + expect(requestBody!.proxyLogin).toBe(proxyLogin); + expect(requestBody!.proxyPassword).toBe(proxyPassword); + }); +}); diff --git a/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.html b/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.html index 072be772b21..607e4987f85 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.html +++ b/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.html @@ -84,7 +84,7 @@ Password - +