From 5c9aad5d29935590cce8f3e12e869ea7efb4d5ed Mon Sep 17 00:00:00 2001 From: kimyenac Date: Sun, 12 Jul 2026 15:15:07 +0900 Subject: [PATCH] [ZEPPELIN-6520] Bind repository proxy password input to proxyPassword control The proxy Password input in the interpreter add-repository modal was bound to the proxyLogin form control, so a proxy password could never be saved and typing one overwrote the proxy login. Bind the Password input to the proxyPassword control so both proxy credentials are submitted. Add a Playwright e2e regression test that fills the proxy login and password and asserts the add-repository request carries each value in its own field. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../models/interpreter-repository-modal.ts | 65 ++++++++++++++++++ ...reate-repository-proxy-credentials.spec.ts | 66 +++++++++++++++++++ .../create-repository-modal.component.html | 2 +- 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 zeppelin-web-angular/e2e/models/interpreter-repository-modal.ts create mode 100644 zeppelin-web-angular/e2e/tests/workspace/interpreter/create-repository-proxy-credentials.spec.ts 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 - +