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() {