From 4cc289b06a6a4af25dd1fbd74baf4f124a5b56bb Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Thu, 12 Jun 2025 14:24:20 +0000 Subject: [PATCH 1/4] password protection widget fix --- .../db-table-row-edit.component.spec.ts | 88 +++++++++++++++++++ .../password/password.component.html | 2 +- .../password/password.component.spec.ts | 65 ++++++++++++++ .../row-fields/password/password.component.ts | 12 ++- 4 files changed, 165 insertions(+), 2 deletions(-) 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..e41c37f97 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 @@ -255,4 +255,92 @@ 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(() => { + (component as any).connectionType = 'postgres'; + 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() { From 76f6157d4405846093c6b76836effe6e3e2a7206 Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Thu, 12 Jun 2025 14:41:34 +0000 Subject: [PATCH 2/4] fix tests --- .../db-table-row-edit/db-table-row-edit.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e41c37f97..faf46aa18 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 @@ -291,7 +291,7 @@ describe('DbTableRowEditComponent', () => { describe('getFormattedUpdatedRow', () => { beforeEach(() => { - (component as any).connectionType = 'postgres'; + spyOnProperty(connectionsService, 'currentConnection').and.returnValue({ type: 'postgres' }); component.tableTypes = {}; component.nonModifyingFields = []; component.pageAction = null; From b520ff4d8be86ebd3b4aa442f0280a192de9791a Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Thu, 12 Jun 2025 14:46:43 +0000 Subject: [PATCH 3/4] fix types --- .../db-table-row-edit/db-table-row-edit.component.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 faf46aa18..58e29ed69 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 } from 'src/app/models/connection'; import { Angulartics2Module } from 'angulartics2'; import { provideHttpClient } from '@angular/common/http'; import { provideRouter } from '@angular/router'; @@ -291,7 +292,7 @@ describe('DbTableRowEditComponent', () => { describe('getFormattedUpdatedRow', () => { beforeEach(() => { - spyOnProperty(connectionsService, 'currentConnection').and.returnValue({ type: 'postgres' }); + spyOnProperty(connectionsService, 'currentConnection').and.returnValue({ type: DBtype.Postgres }); component.tableTypes = {}; component.nonModifyingFields = []; component.pageAction = null; From 2b658b6bd05e0f7ffadac2d2b3549096ef8a27ca Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Thu, 12 Jun 2025 14:53:20 +0000 Subject: [PATCH 4/4] fix types --- .../db-table-row-edit.component.spec.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) 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 58e29ed69..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,7 +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 } from 'src/app/models/connection'; +import { DBtype, Connection, ConnectionType } from 'src/app/models/connection'; import { Angulartics2Module } from 'angulartics2'; import { provideHttpClient } from '@angular/common/http'; import { provideRouter } from '@angular/router'; @@ -292,7 +292,22 @@ describe('DbTableRowEditComponent', () => { describe('getFormattedUpdatedRow', () => { beforeEach(() => { - spyOnProperty(connectionsService, 'currentConnection').and.returnValue({ type: DBtype.Postgres }); + 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;