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 @@
+
+
+ SSO Groups
+
+
+ 0" class="list-group">
+
+ -
+
+
Name
+
Group
+
+
Actions
+
+
+
+ -
+
+
+
{{ getGroupName(ssoGroup.group) }}
+
+
+
+
+
+
+
+
+
+
+
+ No groups defined
+
+
+
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)