From d23b0e72b37ae41fbed72bd6255b30cca8a4ed43 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 28 Sep 2023 23:15:21 -0700 Subject: [PATCH] Re-implement frontend sso groups --- .../users-groups/users-groups.component.html | 49 +- .../users-groups.component.spec.ts | 68 +++ .../users-groups/users-groups.component.ts | 68 +++ .../sso-group-edit-dialog.component.html | 4 +- .../sso-group-edit-dialog.component.spec.ts | 6 +- .../settings/settings.component.spec.ts | 533 ------------------ src-ui/src/app/data/paperless-sso-group.ts | 4 +- .../services/rest/sso-group.service.spec.ts | 4 + 8 files changed, 197 insertions(+), 539 deletions(-) delete mode 100644 src-ui/src/app/components/manage/settings/settings.component.spec.ts create mode 100644 src-ui/src/app/services/rest/sso-group.service.spec.ts diff --git a/src-ui/src/app/components/admin/users-groups/users-groups.component.html b/src-ui/src/app/components/admin/users-groups/users-groups.component.html index daea4cb2f..c9002c9ae 100644 --- a/src-ui/src/app/components/admin/users-groups/users-groups.component.html +++ b/src-ui/src/app/components/admin/users-groups/users-groups.component.html @@ -93,7 +93,54 @@
No groups defined
-
+ +

+ SSO Groups + +

+ + +
No groups defined
+
+ +
Loading...
diff --git a/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts b/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts index fd8961d51..75f71774f 100644 --- a/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts +++ b/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts @@ -25,11 +25,13 @@ import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { PermissionsService } from 'src/app/services/permissions.service' import { GroupService } from 'src/app/services/rest/group.service' import { UserService } from 'src/app/services/rest/user.service' +import { SsoGroupService } from 'src/app/services/rest/sso-group.service' import { SettingsService } from 'src/app/services/settings.service' import { ToastService } from 'src/app/services/toast.service' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component' import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component' +import { SsoGroupEditDialogComponent } from '../../common/edit-dialog/sso-group-edit-dialog/sso-group-edit-dialog.component' import { CheckComponent } from '../../common/input/check/check.component' import { NumberComponent } from '../../common/input/number/number.component' import { PasswordComponent } from '../../common/input/password/password.component' @@ -43,6 +45,7 @@ import { SettingsComponent } from '../settings/settings.component' import { UsersAndGroupsComponent } from './users-groups.component' import { PaperlessUser } from 'src/app/data/paperless-user' import { PaperlessGroup } from 'src/app/data/paperless-group' +import { PaperlessSSOGroup } from 'src/app/data/paperless-sso-group' const users = [ { id: 1, username: 'user1', is_superuser: false }, @@ -52,6 +55,10 @@ const groups = [ { id: 1, name: 'group1' }, { id: 2, name: 'group2' }, ] +const ssoGroups = [ + { id: 1, name: 'sso group1', group: 1 }, + { id: 2, name: 'sso group2', group: 2 }, +] describe('UsersAndGroupsComponent', () => { let component: UsersAndGroupsComponent @@ -62,6 +69,7 @@ describe('UsersAndGroupsComponent', () => { let userService: UserService let permissionsService: PermissionsService let groupService: GroupService + let ssoGroupService: SsoGroupService beforeEach(() => { TestBed.configureTestingModule({ @@ -109,6 +117,7 @@ describe('UsersAndGroupsComponent', () => { .spyOn(permissionsService, 'currentUserOwnsObject') .mockReturnValue(true) groupService = TestBed.inject(GroupService) + ssoGroupService = TestBed.inject(SsoGroupService) component = fixture.componentInstance fixture.detectChanges() }) @@ -132,6 +141,15 @@ describe('UsersAndGroupsComponent', () => { }) ) } + if (excludeService !== ssoGroupService) { + jest.spyOn(ssoGroupService, 'listAll').mockReturnValue( + of({ + all: ssoGroups.map((r) => r.id), + count: ssoGroups.length, + results: (ssoGroups as PaperlessSSOGroup[]).concat([]), + }) + ) + } fixture = TestBed.createComponent(UsersAndGroupsComponent) component = fixture.componentInstance @@ -264,4 +282,54 @@ describe('UsersAndGroupsComponent', () => { fixture.detectChanges() expect(toastErrorSpy).toBeCalled() }) + + it('should show errors on load if load sso groups failure', () => { + const toastErrorSpy = jest.spyOn(toastService, 'showError') + jest + .spyOn(ssoGroupService, 'listAll') + .mockImplementation(() => + throwError(() => new Error('failed to load SSO groups')) + ) + completeSetup(ssoGroupService) + fixture.detectChanges() + expect(toastErrorSpy).toBeCalled() + }) + + it('should support edit / create sso group, show error if needed', () => { + completeSetup() + let modal: NgbModalRef + modalService.activeInstances.subscribe((refs) => (modal = refs[0])) + component.editSsoGroup(ssoGroups[0]) + const editDialog = modal.componentInstance as SsoGroupEditDialogComponent + const toastErrorSpy = jest.spyOn(toastService, 'showError') + const toastInfoSpy = jest.spyOn(toastService, 'showInfo') + editDialog.failed.emit() + expect(toastErrorSpy).toBeCalled() + editDialog.succeeded.emit(ssoGroups[0]) + expect(toastInfoSpy).toHaveBeenCalledWith( + `Saved SSO group "${ssoGroups[0].name}".` + ) + component.editSsoGroup() + }) + + it('should support delete sso group, show error if needed', () => { + completeSetup() + let modal: NgbModalRef + modalService.activeInstances.subscribe((refs) => (modal = refs[0])) + component.deleteSsoGroup(ssoGroups[0]) + const deleteDialog = modal.componentInstance as ConfirmDialogComponent + const deleteSpy = jest.spyOn(ssoGroupService, 'delete') + const toastErrorSpy = jest.spyOn(toastService, 'showError') + const toastInfoSpy = jest.spyOn(toastService, 'showInfo') + const listAllSpy = jest.spyOn(ssoGroupService, 'listAll') + deleteSpy.mockReturnValueOnce( + throwError(() => new Error('error deleting SSO group')) + ) + deleteDialog.confirm() + expect(toastErrorSpy).toBeCalled() + deleteSpy.mockReturnValueOnce(of(true)) + deleteDialog.confirm() + expect(listAllSpy).toHaveBeenCalled() + expect(toastInfoSpy).toHaveBeenCalledWith('Deleted SSO group') + }) }) diff --git a/src-ui/src/app/components/admin/users-groups/users-groups.component.ts b/src-ui/src/app/components/admin/users-groups/users-groups.component.ts index a9ce1d600..8684cb4db 100644 --- a/src-ui/src/app/components/admin/users-groups/users-groups.component.ts +++ b/src-ui/src/app/components/admin/users-groups/users-groups.component.ts @@ -3,14 +3,17 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { Subject, first, takeUntil } from 'rxjs' import { PaperlessGroup } from 'src/app/data/paperless-group' import { PaperlessUser } from 'src/app/data/paperless-user' +import { PaperlessSSOGroup } from 'src/app/data/paperless-sso-group' import { PermissionsService } from 'src/app/services/permissions.service' import { GroupService } from 'src/app/services/rest/group.service' import { UserService } from 'src/app/services/rest/user.service' +import { SsoGroupService } from 'src/app/services/rest/sso-group.service' import { ToastService } from 'src/app/services/toast.service' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component' import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component' +import { SsoGroupEditDialogComponent } from '../../common/edit-dialog/sso-group-edit-dialog/sso-group-edit-dialog.component' import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component' import { SettingsService } from 'src/app/services/settings.service' @@ -25,12 +28,14 @@ export class UsersAndGroupsComponent { users: PaperlessUser[] groups: PaperlessGroup[] + ssoGroups: PaperlessSSOGroup[] unsubscribeNotifier: Subject = new Subject() constructor( private usersService: UserService, private groupsService: GroupService, + private ssoGroupService: SsoGroupService, private toastService: ToastService, private modalService: NgbModal, public permissionsService: PermissionsService, @@ -63,6 +68,18 @@ export class UsersAndGroupsComponent this.toastService.showError($localize`Error retrieving groups`, e) }, }) + + this.ssoGroupService + .listAll(null, null, { full_perms: true }) + .pipe(first(), takeUntil(this.unsubscribeNotifier)) + .subscribe({ + next: (r) => { + this.ssoGroups = r.results + }, + error: (e) => { + this.toastService.showError($localize`Error retrieving SSO groups`, e) + }, + }) } ngOnDestroy() { @@ -183,6 +200,57 @@ export class UsersAndGroupsComponent }) } + editSsoGroup(ssoGroup: PaperlessSSOGroup = null) { + var modal = this.modalService.open(SsoGroupEditDialogComponent, { + backdrop: 'static', + }) + modal.componentInstance.dialogMode = ssoGroup + ? EditDialogMode.EDIT + : EditDialogMode.CREATE + modal.componentInstance.object = ssoGroup + modal.componentInstance.succeeded + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe((newGroup) => { + this.toastService.showInfo( + $localize`Saved SSO group "${newGroup.name}".` + ) + this.ssoGroupService.listAll().subscribe((r) => { + this.ssoGroups = r.results + }) + }) + modal.componentInstance.failed + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe((e) => { + this.toastService.showError($localize`Error saving SSO group.`, e) + }) + } + + deleteSsoGroup(ssoGroup: PaperlessSSOGroup) { + let modal = this.modalService.open(ConfirmDialogComponent, { + backdrop: 'static', + }) + modal.componentInstance.title = $localize`Confirm delete SSO group` + modal.componentInstance.messageBold = $localize`This operation will permanently delete this SSO group.` + modal.componentInstance.message = $localize`This operation cannot be undone.` + modal.componentInstance.btnClass = 'btn-danger' + modal.componentInstance.btnCaption = $localize`Proceed` + modal.componentInstance.confirmClicked.subscribe(() => { + modal.componentInstance.buttonsEnabled = false + this.ssoGroupService.delete(ssoGroup).subscribe({ + next: () => { + modal.close() + this.toastService.showInfo($localize`Deleted SSO group`) + this.ssoGroupService.listAll().subscribe((r) => { + this.ssoGroups = r.results + }) + }, + error: (e) => { + this.toastService.showError($localize`Error deleting SSO group.`, e) + }, + }) + }) + } + getGroupName(id: number): string { return this.groups?.find((g) => g.id === id)?.name ?? '' } diff --git a/src-ui/src/app/components/common/edit-dialog/sso-group-edit-dialog/sso-group-edit-dialog.component.html b/src-ui/src/app/components/common/edit-dialog/sso-group-edit-dialog/sso-group-edit-dialog.component.html index c3b46a2c8..ba8ddcfff 100644 --- a/src-ui/src/app/components/common/edit-dialog/sso-group-edit-dialog/sso-group-edit-dialog.component.html +++ b/src-ui/src/app/components/common/edit-dialog/sso-group-edit-dialog/sso-group-edit-dialog.component.html @@ -7,8 +7,8 @@ diff --git a/src-ui/src/app/components/common/edit-dialog/sso-group-edit-dialog/sso-group-edit-dialog.component.spec.ts b/src-ui/src/app/components/common/edit-dialog/sso-group-edit-dialog/sso-group-edit-dialog.component.spec.ts index 3a7ed4d35..9b430a2be 100644 --- a/src-ui/src/app/components/common/edit-dialog/sso-group-edit-dialog/sso-group-edit-dialog.component.spec.ts +++ b/src-ui/src/app/components/common/edit-dialog/sso-group-edit-dialog/sso-group-edit-dialog.component.spec.ts @@ -11,10 +11,12 @@ import { NgSelectModule } from '@ng-select/ng-select' import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component' import { SsoGroupEditDialogComponent } from './sso-group-edit-dialog.component' import { PermissionsSelectComponent } from '../../permissions-select/permissions-select.component' +import { SettingsService } from 'src/app/services/settings.service' -describe('GroupEditDialogComponent', () => { +describe('SSoGroupEditDialogComponent', () => { let component: SsoGroupEditDialogComponent let fixture: ComponentFixture + let settingsService: SettingsService beforeEach(async () => { TestBed.configureTestingModule({ @@ -38,6 +40,8 @@ describe('GroupEditDialogComponent', () => { }).compileComponents() fixture = TestBed.createComponent(SsoGroupEditDialogComponent) + settingsService = TestBed.inject(SettingsService) + settingsService.currentUser = { id: 99, username: 'user99' } component = fixture.componentInstance fixture.detectChanges() diff --git a/src-ui/src/app/components/manage/settings/settings.component.spec.ts b/src-ui/src/app/components/manage/settings/settings.component.spec.ts deleted file mode 100644 index 5f6833d93..000000000 --- a/src-ui/src/app/components/manage/settings/settings.component.spec.ts +++ /dev/null @@ -1,533 +0,0 @@ -import { ViewportScroller, DatePipe } from '@angular/common' -import { HttpClientTestingModule } from '@angular/common/http/testing' -import { - ComponentFixture, - TestBed, - fakeAsync, - tick, -} from '@angular/core/testing' -import { FormsModule, ReactiveFormsModule } from '@angular/forms' -import { By } from '@angular/platform-browser' -import { Router, ActivatedRoute, convertToParamMap } from '@angular/router' -import { RouterTestingModule } from '@angular/router/testing' -import { - NgbModal, - NgbModule, - NgbNavLink, - NgbModalRef, -} from '@ng-bootstrap/ng-bootstrap' -import { of, throwError } from 'rxjs' -import { routes } from 'src/app/app-routing.module' -import { PaperlessMailAccount } from 'src/app/data/paperless-mail-account' -import { PaperlessMailRule } from 'src/app/data/paperless-mail-rule' -import { PaperlessSavedView } from 'src/app/data/paperless-saved-view' -import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' -import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' -import { PermissionsGuard } from 'src/app/guards/permissions.guard' -import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' -import { PermissionsService } from 'src/app/services/permissions.service' -import { GroupService } from 'src/app/services/rest/group.service' -import { MailAccountService } from 'src/app/services/rest/mail-account.service' -import { MailRuleService } from 'src/app/services/rest/mail-rule.service' -import { SavedViewService } from 'src/app/services/rest/saved-view.service' -import { UserService } from 'src/app/services/rest/user.service' -import { SettingsService } from 'src/app/services/settings.service' -import { ToastService, Toast } from 'src/app/services/toast.service' -import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' -import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component' -import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component' -import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component' -import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component' -import { CheckComponent } from '../../common/input/check/check.component' -import { ColorComponent } from '../../common/input/color/color.component' -import { PageHeaderComponent } from '../../common/page-header/page-header.component' -import { SettingsComponent } from './settings.component' -import { SsoGroupService } from '../../../services/rest/sso-group.service' -import { SsoGroupEditDialogComponent } from '../../common/edit-dialog/sso-group-edit-dialog/sso-group-edit-dialog.component' - -const savedViews = [ - { id: 1, name: 'view1' }, - { id: 2, name: 'view2' }, -] -const users = [ - { id: 1, username: 'user1', is_superuser: false }, - { id: 2, username: 'user2', is_superuser: false }, -] -const groups = [ - { id: 1, name: 'group1' }, - { id: 2, name: 'group2' }, -] -const sso_groups = [ - { id: 1, name: 'sso_group_1', group: 1 }, - { id: 1, name: 'sso_group_2', group: 2 }, -] -const mailAccounts = [ - { id: 1, name: 'account1' }, - { id: 2, name: 'account2' }, -] -const mailRules = [ - { id: 1, name: 'rule1', owner: 1 }, - { id: 2, name: 'rule2', owner: 2 }, -] - -describe('SettingsComponent', () => { - let component: SettingsComponent - let fixture: ComponentFixture - let modalService: NgbModal - let router: Router - let settingsService: SettingsService - let savedViewService: SavedViewService - let activatedRoute: ActivatedRoute - let viewportScroller: ViewportScroller - let toastService: ToastService - let userService: UserService - let permissionsService: PermissionsService - let groupService: GroupService - let ssoGroupService: SsoGroupService - let mailAccountService: MailAccountService - let mailRuleService: MailRuleService - - beforeEach(async () => { - TestBed.configureTestingModule({ - declarations: [ - SettingsComponent, - PageHeaderComponent, - IfPermissionsDirective, - CustomDatePipe, - ConfirmDialogComponent, - CheckComponent, - ColorComponent, - ], - providers: [CustomDatePipe, DatePipe, PermissionsGuard], - imports: [ - NgbModule, - HttpClientTestingModule, - RouterTestingModule.withRoutes(routes), - FormsModule, - ReactiveFormsModule, - ], - }).compileComponents() - - modalService = TestBed.inject(NgbModal) - router = TestBed.inject(Router) - activatedRoute = TestBed.inject(ActivatedRoute) - viewportScroller = TestBed.inject(ViewportScroller) - toastService = TestBed.inject(ToastService) - settingsService = TestBed.inject(SettingsService) - userService = TestBed.inject(UserService) - permissionsService = TestBed.inject(PermissionsService) - jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) - jest - .spyOn(permissionsService, 'currentUserHasObjectPermissions') - .mockReturnValue(true) - jest - .spyOn(permissionsService, 'currentUserOwnsObject') - .mockReturnValue(true) - jest.spyOn(userService, 'listAll').mockReturnValue( - of({ - all: users.map((u) => u.id), - count: users.length, - results: users.concat([]), - }) - ) - groupService = TestBed.inject(GroupService) - jest.spyOn(groupService, 'listAll').mockReturnValue( - of({ - all: groups.map((g) => g.id), - count: groups.length, - results: groups.concat([]), - }) - ) - ssoGroupService = TestBed.inject(SsoGroupService) - jest.spyOn(ssoGroupService, 'listAll').mockReturnValue( - of({ - all: sso_groups.map((g) => g.id), - count: sso_groups.length, - results: sso_groups.concat([]), - }) - ) - savedViewService = TestBed.inject(SavedViewService) - jest.spyOn(savedViewService, 'listAll').mockReturnValue( - of({ - all: savedViews.map((v) => v.id), - count: savedViews.length, - results: (savedViews as PaperlessSavedView[]).concat([]), - }) - ) - mailAccountService = TestBed.inject(MailAccountService) - jest.spyOn(mailAccountService, 'listAll').mockReturnValue( - of({ - all: mailAccounts.map((a) => a.id), - count: mailAccounts.length, - results: (mailAccounts as PaperlessMailAccount[]).concat([]), - }) - ) - mailRuleService = TestBed.inject(MailRuleService) - jest.spyOn(mailRuleService, 'listAll').mockReturnValue( - of({ - all: mailRules.map((r) => r.id), - count: mailRules.length, - results: (mailRules as PaperlessMailRule[]).concat([]), - }) - ) - - fixture = TestBed.createComponent(SettingsComponent) - component = fixture.componentInstance - fixture.detectChanges() - }) - - it('should support tabbed settings & change URL, prevent navigation if dirty confirmation rejected', () => { - const navigateSpy = jest.spyOn(router, 'navigate') - const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink)) - tabButtons[1].nativeElement.dispatchEvent(new MouseEvent('click')) - expect(navigateSpy).toHaveBeenCalledWith(['settings', 'notifications']) - tabButtons[2].nativeElement.dispatchEvent(new MouseEvent('click')) - expect(navigateSpy).toHaveBeenCalledWith(['settings', 'savedviews']) - tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click')) - expect(navigateSpy).toHaveBeenCalledWith(['settings', 'mail']) - tabButtons[4].nativeElement.dispatchEvent(new MouseEvent('click')) - expect(navigateSpy).toHaveBeenCalledWith(['settings', 'usersgroups']) - - const initSpy = jest.spyOn(component, 'initialize') - component.isDirty = true // mock dirty - navigateSpy.mockResolvedValueOnce(false) // nav rejected cause dirty - tabButtons[0].nativeElement.dispatchEvent(new MouseEvent('click')) - expect(navigateSpy).toHaveBeenCalledWith(['settings', 'general']) - expect(initSpy).not.toHaveBeenCalled() - - navigateSpy.mockResolvedValueOnce(true) // nav accepted even though dirty - tabButtons[1].nativeElement.dispatchEvent(new MouseEvent('click')) - expect(navigateSpy).toHaveBeenCalledWith(['settings', 'notifications']) - expect(initSpy).toHaveBeenCalled() - }) - - it('should support direct link to tab by URL, scroll if needed', () => { - jest - .spyOn(activatedRoute, 'paramMap', 'get') - .mockReturnValue(of(convertToParamMap({ section: 'mail' }))) - activatedRoute.snapshot.fragment = '#mail' - const scrollSpy = jest.spyOn(viewportScroller, 'scrollToAnchor') - component.ngOnInit() - expect(component.activeNavID).toEqual(4) // Mail - component.ngAfterViewInit() - expect(scrollSpy).toHaveBeenCalledWith('#mail') - }) - - it('should lazy load tab data', () => { - const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink)) - - expect(component.savedViews).toBeUndefined() - tabButtons[2].nativeElement.dispatchEvent( - new MouseEvent('mouseover', { bubbles: true }) - ) - expect(component.savedViews).not.toBeUndefined() - - expect(component.mailAccounts).toBeUndefined() - tabButtons[3].nativeElement.dispatchEvent( - new MouseEvent('mouseover', { bubbles: true }) - ) - expect(component.mailAccounts).not.toBeUndefined() - - expect(component.users).toBeUndefined() - tabButtons[4].nativeElement.dispatchEvent( - new MouseEvent('mouseover', { bubbles: true }) - ) - expect(component.users).not.toBeUndefined() - }) - - it('should support save saved views, show error', () => { - component.maybeInitializeTab(3) // SavedViews - - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastSpy = jest.spyOn(toastService, 'show') - const savedViewPatchSpy = jest.spyOn(savedViewService, 'patchMany') - - // saved views error first - savedViewPatchSpy.mockReturnValueOnce( - throwError(() => new Error('unable to save saved views')) - ) - component.saveSettings() - expect(toastErrorSpy).toHaveBeenCalled() - expect(savedViewPatchSpy).toHaveBeenCalled() - toastSpy.mockClear() - toastErrorSpy.mockClear() - savedViewPatchSpy.mockClear() - - // succeed saved views - savedViewPatchSpy.mockReturnValueOnce( - of(savedViews as PaperlessSavedView[]) - ) - component.saveSettings() - expect(toastErrorSpy).not.toHaveBeenCalled() - expect(savedViewPatchSpy).toHaveBeenCalled() - }) - - it('should support save local settings updating appearance settings and calling API, show error', () => { - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastSpy = jest.spyOn(toastService, 'show') - const storeSpy = jest.spyOn(settingsService, 'storeSettings') - const appearanceSettingsSpy = jest.spyOn( - settingsService, - 'updateAppearanceSettings' - ) - const setSpy = jest.spyOn(settingsService, 'set') - - // error first - storeSpy.mockReturnValueOnce( - throwError(() => new Error('unable to save settings')) - ) - component.saveSettings() - expect(toastErrorSpy).toHaveBeenCalled() - expect(storeSpy).toHaveBeenCalled() - expect(appearanceSettingsSpy).not.toHaveBeenCalled() - expect(setSpy).toHaveBeenCalledTimes(19) - - // succeed - storeSpy.mockReturnValueOnce(of(true)) - component.saveSettings() - expect(toastSpy).toHaveBeenCalled() - expect(appearanceSettingsSpy).toHaveBeenCalled() - }) - - it('should offer reload if settings changes require', () => { - let toast: Toast - toastService.getToasts().subscribe((t) => (toast = t[0])) - component.initialize(true) // reset - component.store.getValue()['displayLanguage'] = 'en-US' - component.store.getValue()['updateCheckingEnabled'] = false - component.settingsForm.value.displayLanguage = 'en-GB' - component.settingsForm.value.updateCheckingEnabled = true - jest.spyOn(settingsService, 'storeSettings').mockReturnValueOnce(of(true)) - component.saveSettings() - expect(toast.actionName).toEqual('Reload now') - }) - - it('should allow setting theme color, visually apply change immediately but not save', () => { - const appearanceSpy = jest.spyOn( - settingsService, - 'updateAppearanceSettings' - ) - const colorInput = fixture.debugElement.query(By.directive(ColorComponent)) - colorInput.query(By.css('input')).nativeElement.value = '#ff0000' - colorInput - .query(By.css('input')) - .nativeElement.dispatchEvent(new Event('change')) - fixture.detectChanges() - expect(appearanceSpy).toHaveBeenCalled() - expect(settingsService.get(SETTINGS_KEYS.THEME_COLOR)).toEqual('') - component.clearThemeColor() - }) - - it('should support delete saved view', () => { - component.maybeInitializeTab(3) // SavedViews - const toastSpy = jest.spyOn(toastService, 'showInfo') - const deleteSpy = jest.spyOn(savedViewService, 'delete') - deleteSpy.mockReturnValue(of(true)) - component.deleteSavedView(savedViews[0] as PaperlessSavedView) - expect(deleteSpy).toHaveBeenCalled() - expect(toastSpy).toHaveBeenCalledWith( - `Saved view "${savedViews[0].name}" deleted.` - ) - }) - - it('should support edit / create user, show error if needed', () => { - let modal: NgbModalRef - modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.editUser(users[0]) - const editDialog = modal.componentInstance as UserEditDialogComponent - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastInfoSpy = jest.spyOn(toastService, 'showInfo') - editDialog.failed.emit() - expect(toastErrorSpy).toBeCalled() - settingsService.currentUser = users[1] // simulate logged in as different user - editDialog.succeeded.emit(users[0]) - expect(toastInfoSpy).toHaveBeenCalledWith( - `Saved user "${users[0].username}".` - ) - }) - - it('should support delete user, show error if needed', () => { - let modal: NgbModalRef - modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.deleteUser(users[0]) - const deleteDialog = modal.componentInstance as ConfirmDialogComponent - const deleteSpy = jest.spyOn(userService, 'delete') - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastInfoSpy = jest.spyOn(toastService, 'showInfo') - const listAllSpy = jest.spyOn(userService, 'listAll') - deleteSpy.mockReturnValueOnce( - throwError(() => new Error('error deleting user')) - ) - deleteDialog.confirm() - expect(toastErrorSpy).toBeCalled() - deleteSpy.mockReturnValueOnce(of(true)) - deleteDialog.confirm() - expect(listAllSpy).toHaveBeenCalled() - expect(toastInfoSpy).toHaveBeenCalledWith('Deleted user') - }) - - it('should logout current user if password changed, after delay', fakeAsync(() => { - let modal: NgbModalRef - modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.editUser(users[0]) - const editDialog = modal.componentInstance as UserEditDialogComponent - editDialog.passwordIsSet = true - settingsService.currentUser = users[0] // simulate logged in as same user - editDialog.succeeded.emit(users[0]) - fixture.detectChanges() - Object.defineProperty(window, 'location', { - value: { - href: 'http://localhost/', - }, - writable: true, // possibility to override - }) - tick(2600) - expect(window.location.href).toContain('logout') - })) - - it('should support edit / create group, show error if needed', () => { - let modal: NgbModalRef - modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.editGroup(groups[0]) - const editDialog = modal.componentInstance as GroupEditDialogComponent - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastInfoSpy = jest.spyOn(toastService, 'showInfo') - editDialog.failed.emit() - expect(toastErrorSpy).toBeCalled() - editDialog.succeeded.emit(groups[0]) - expect(toastInfoSpy).toHaveBeenCalledWith( - `Saved group "${groups[0].name}".` - ) - }) - - it('should support delete group, show error if needed', () => { - let modal: NgbModalRef - modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.deleteGroup(users[0]) - const deleteDialog = modal.componentInstance as ConfirmDialogComponent - const deleteSpy = jest.spyOn(groupService, 'delete') - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastInfoSpy = jest.spyOn(toastService, 'showInfo') - const listAllSpy = jest.spyOn(groupService, 'listAll') - deleteSpy.mockReturnValueOnce( - throwError(() => new Error('error deleting group')) - ) - deleteDialog.confirm() - expect(toastErrorSpy).toBeCalled() - deleteSpy.mockReturnValueOnce(of(true)) - deleteDialog.confirm() - expect(listAllSpy).toHaveBeenCalled() - expect(toastInfoSpy).toHaveBeenCalledWith('Deleted group') - }) - - it('should get group name', () => { - component.maybeInitializeTab(5) // UsersGroups - expect(component.getGroupName(1)).toEqual(groups[0].name) - expect(component.getGroupName(11)).toEqual('') - }) - - it('should support edit / create mail account, show error if needed', () => { - let modal: NgbModalRef - modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.editMailAccount(mailAccounts[0] as PaperlessMailAccount) - const editDialog = modal.componentInstance as MailAccountEditDialogComponent - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastInfoSpy = jest.spyOn(toastService, 'showInfo') - editDialog.failed.emit() - expect(toastErrorSpy).toBeCalled() - editDialog.succeeded.emit(mailAccounts[0]) - expect(toastInfoSpy).toHaveBeenCalledWith( - `Saved account "${mailAccounts[0].name}".` - ) - }) - - it('should support delete mail account, show error if needed', () => { - let modal: NgbModalRef - modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.deleteMailAccount(mailAccounts[0] as PaperlessMailAccount) - const deleteDialog = modal.componentInstance as ConfirmDialogComponent - const deleteSpy = jest.spyOn(mailAccountService, 'delete') - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastInfoSpy = jest.spyOn(toastService, 'showInfo') - const listAllSpy = jest.spyOn(mailAccountService, 'listAll') - deleteSpy.mockReturnValueOnce( - throwError(() => new Error('error deleting mail account')) - ) - deleteDialog.confirm() - expect(toastErrorSpy).toBeCalled() - deleteSpy.mockReturnValueOnce(of(true)) - deleteDialog.confirm() - expect(listAllSpy).toHaveBeenCalled() - expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail account') - }) - - it('should support edit / create mail rule, show error if needed', () => { - let modal: NgbModalRef - modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.editMailRule(mailRules[0] as PaperlessMailRule) - const editDialog = modal.componentInstance as MailRuleEditDialogComponent - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastInfoSpy = jest.spyOn(toastService, 'showInfo') - editDialog.failed.emit() - expect(toastErrorSpy).toBeCalled() - editDialog.succeeded.emit(mailRules[0]) - expect(toastInfoSpy).toHaveBeenCalledWith( - `Saved rule "${mailRules[0].name}".` - ) - }) - - it('should support delete mail rule, show error if needed', () => { - let modal: NgbModalRef - modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.deleteMailRule(mailRules[0] as PaperlessMailRule) - const deleteDialog = modal.componentInstance as ConfirmDialogComponent - const deleteSpy = jest.spyOn(mailRuleService, 'delete') - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastInfoSpy = jest.spyOn(toastService, 'showInfo') - const listAllSpy = jest.spyOn(mailRuleService, 'listAll') - deleteSpy.mockReturnValueOnce( - throwError(() => new Error('error deleting mail rule')) - ) - deleteDialog.confirm() - expect(toastErrorSpy).toBeCalled() - deleteSpy.mockReturnValueOnce(of(true)) - deleteDialog.confirm() - expect(listAllSpy).toHaveBeenCalled() - expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail rule') - }) - - it('should support edit / create sso group, show error if needed', () => { - let modal: NgbModalRef - modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.editSSOGroup(sso_groups[0]) - const editDialog = modal.componentInstance as SsoGroupEditDialogComponent - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastInfoSpy = jest.spyOn(toastService, 'showInfo') - editDialog.failed.emit() - expect(toastErrorSpy).toBeCalled() - editDialog.succeeded.emit(sso_groups[0]) - expect(toastInfoSpy).toHaveBeenCalledWith( - `Saved SSO group "${sso_groups[0].name}".` - ) - }) - - it('should support delete SSO group, show error if needed', () => { - let modal: NgbModalRef - modalService.activeInstances.subscribe((refs) => (modal = refs[0])) - component.deleteSSOGroup(sso_groups[0]) - const deleteDialog = modal.componentInstance as ConfirmDialogComponent - const deleteSpy = jest.spyOn(ssoGroupService, 'delete') - const toastErrorSpy = jest.spyOn(toastService, 'showError') - const toastInfoSpy = jest.spyOn(toastService, 'showInfo') - const listAllSpy = jest.spyOn(ssoGroupService, 'listAll') - deleteSpy.mockReturnValueOnce( - throwError(() => new Error('error deleting sso group')) - ) - deleteDialog.confirm() - expect(toastErrorSpy).toBeCalled() - deleteSpy.mockReturnValueOnce(of(true)) - deleteDialog.confirm() - expect(listAllSpy).toHaveBeenCalled() - expect(toastInfoSpy).toHaveBeenCalledWith('Deleted SSO group') - }) -}) diff --git a/src-ui/src/app/data/paperless-sso-group.ts b/src-ui/src/app/data/paperless-sso-group.ts index ac0eaf650..88ed44b45 100644 --- a/src-ui/src/app/data/paperless-sso-group.ts +++ b/src-ui/src/app/data/paperless-sso-group.ts @@ -1,6 +1,6 @@ import { ObjectWithId } from './object-with-id' export interface PaperlessSSOGroup extends ObjectWithId { - name?: string - group?: number + name: string + group: number } diff --git a/src-ui/src/app/services/rest/sso-group.service.spec.ts b/src-ui/src/app/services/rest/sso-group.service.spec.ts new file mode 100644 index 000000000..03c71a6f1 --- /dev/null +++ b/src-ui/src/app/services/rest/sso-group.service.spec.ts @@ -0,0 +1,4 @@ +import { SsoGroupService } from './sso-group.service' +import { commonAbstractNameFilterPaperlessServiceTests } from './abstract-name-filter-service.spec' + +commonAbstractNameFilterPaperlessServiceTests('sso_groups', SsoGroupService)