diff --git a/frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.spec.ts b/frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.spec.ts
index 8f2965d81..e51d86e57 100644
--- a/frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.spec.ts
+++ b/frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.spec.ts
@@ -5,6 +5,7 @@ import { DbTableRowEditComponent } from './db-table-row-edit.component';
import { MatDialogModule } from '@angular/material/dialog';
import { TablesService } from 'src/app/services/tables.service';
import { ConnectionsService } from 'src/app/services/connections.service';
+import { DBtype, Connection, ConnectionType } from 'src/app/models/connection';
import { Angulartics2Module } from 'angulartics2';
import { provideHttpClient } from '@angular/common/http';
import { provideRouter } from '@angular/router';
@@ -255,4 +256,107 @@ describe('DbTableRowEditComponent', () => {
const isPriceWidget = component.isWidget('Price');
expect(isPriceWidget).toBeTrue();
});
+
+ describe('updateField for password widget behavior', () => {
+ beforeEach(() => {
+ component.tableRowValues = {
+ id: 1,
+ username: 'testuser',
+ password: '***'
+ };
+ });
+
+ it('should update tableRowValues when password field receives a value', () => {
+ component.updateField('newPassword', 'password');
+ expect(component.tableRowValues['password']).toBe('newPassword');
+ });
+
+ it('should update tableRowValues when password field receives empty string', () => {
+ component.updateField('', 'password');
+ expect(component.tableRowValues['password']).toBe('');
+ });
+
+ it('should update tableRowValues when password field receives null (clear password)', () => {
+ component.updateField(null, 'password');
+ expect(component.tableRowValues['password']).toBe(null);
+ });
+
+ it('should handle password field update alongside other fields', () => {
+ component.updateField('updatedUser', 'username');
+ component.updateField('newPassword', 'password');
+
+ expect(component.tableRowValues['username']).toBe('updatedUser');
+ expect(component.tableRowValues['password']).toBe('newPassword');
+ });
+ });
+
+ describe('getFormattedUpdatedRow', () => {
+ beforeEach(() => {
+ spyOnProperty(connectionsService, 'currentConnection').and.returnValue({
+ id: 'test-id',
+ database: 'test-db',
+ title: 'Test Connection',
+ host: 'localhost',
+ port: '5432',
+ sid: null,
+ type: DBtype.Postgres,
+ username: 'test-user',
+ ssh: false,
+ ssl: false,
+ cert: '',
+ masterEncryption: false,
+ azure_encryption: false,
+ connectionType: ConnectionType.Direct
+ } as Connection);
+ component.tableTypes = {};
+ component.nonModifyingFields = [];
+ component.pageAction = null;
+ });
+
+ it('should include password field when it has a value', () => {
+ component.tableRowValues = {
+ id: 1,
+ username: 'testuser',
+ password: 'newPassword'
+ };
+
+ const result = component.getFormattedUpdatedRow();
+ expect((result as any).password).toBe('newPassword');
+ });
+
+ it('should include password field when it is null (explicit clear)', () => {
+ component.tableRowValues = {
+ id: 1,
+ username: 'testuser',
+ password: null
+ };
+
+ const result = component.getFormattedUpdatedRow();
+ expect((result as any).password).toBe(null);
+ });
+
+ it('should include password field when it is empty string', () => {
+ component.tableRowValues = {
+ id: 1,
+ username: 'testuser',
+ password: ''
+ };
+
+ const result = component.getFormattedUpdatedRow();
+ expect((result as any).password).toBe('');
+ });
+
+ it('should preserve other fields when password is empty', () => {
+ component.tableRowValues = {
+ id: 1,
+ username: 'testuser',
+ password: ''
+ };
+
+ const result = component.getFormattedUpdatedRow();
+ expect((result as any).id).toBe(1);
+ expect((result as any).username).toBe('testuser');
+ expect((result as any).password).toBe('');
+ });
+ });
});
diff --git a/frontend/src/app/components/ui-components/row-fields/password/password.component.html b/frontend/src/app/components/ui-components/row-fields/password/password.component.html
index c7ec94221..040618f8d 100644
--- a/frontend/src/app/components/ui-components/row-fields/password/password.component.html
+++ b/frontend/src/app/components/ui-components/row-fields/password/password.component.html
@@ -2,7 +2,7 @@
{{normalizedLabel}}
+ [(ngModel)]="value" (ngModelChange)="onPasswordChange($event)">
To keep password the same keep this field blank.
{
component.onClearPasswordChange();
expect(event).toHaveBeenCalledWith(null);
});
+
+ describe('ngOnInit', () => {
+ it('should reset masked password value to empty string', () => {
+ component.value = '***';
+ component.ngOnInit();
+ expect(component.value).toBe('');
+ });
+
+ it('should not emit onFieldChange when password is masked (empty after reset)', () => {
+ const event = spyOn(component.onFieldChange, 'emit');
+ component.value = '***';
+ component.ngOnInit();
+ expect(event).not.toHaveBeenCalled();
+ });
+
+ it('should emit onFieldChange when password has actual value', () => {
+ const event = spyOn(component.onFieldChange, 'emit');
+ component.value = 'actualPassword';
+ component.ngOnInit();
+ expect(event).toHaveBeenCalledWith('actualPassword');
+ });
+
+ it('should not emit onFieldChange when password is empty string', () => {
+ const event = spyOn(component.onFieldChange, 'emit');
+ component.value = '';
+ component.ngOnInit();
+ expect(event).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('onPasswordChange', () => {
+ it('should emit onFieldChange when password has value', () => {
+ const event = spyOn(component.onFieldChange, 'emit');
+ component.onPasswordChange('newPassword');
+ expect(event).toHaveBeenCalledWith('newPassword');
+ });
+
+ it('should not emit onFieldChange when password is empty string', () => {
+ const event = spyOn(component.onFieldChange, 'emit');
+ component.onPasswordChange('');
+ expect(event).not.toHaveBeenCalled();
+ });
+
+ it('should emit onFieldChange when password is whitespace (actual value)', () => {
+ const event = spyOn(component.onFieldChange, 'emit');
+ component.onPasswordChange(' ');
+ expect(event).toHaveBeenCalledWith(' ');
+ });
+ });
+
+ describe('onClearPasswordChange', () => {
+ it('should emit null when clearPassword is true', () => {
+ const event = spyOn(component.onFieldChange, 'emit');
+ component.clearPassword = true;
+ component.onClearPasswordChange();
+ expect(event).toHaveBeenCalledWith(null);
+ });
+
+ it('should not emit when clearPassword is false', () => {
+ const event = spyOn(component.onFieldChange, 'emit');
+ component.clearPassword = false;
+ component.onClearPasswordChange();
+ expect(event).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/frontend/src/app/components/ui-components/row-fields/password/password.component.ts b/frontend/src/app/components/ui-components/row-fields/password/password.component.ts
index de4dacf6c..049cd44fe 100644
--- a/frontend/src/app/components/ui-components/row-fields/password/password.component.ts
+++ b/frontend/src/app/components/ui-components/row-fields/password/password.component.ts
@@ -25,7 +25,17 @@ export class PasswordRowComponent extends BaseRowFieldComponent {
ngOnInit(): void {
super.ngOnInit();
if (this.value === '***') this.value = '';
- this.onFieldChange.emit(this.value);
+ // Don't emit empty password value to skip sending it to backend
+ if (this.value !== '') {
+ this.onFieldChange.emit(this.value);
+ }
+ }
+
+ onPasswordChange(newValue: string) {
+ // Only emit non-empty values to prevent sending empty strings to backend
+ if (newValue !== '') {
+ this.onFieldChange.emit(newValue);
+ }
}
onClearPasswordChange() {