import { DatePipe } from '@angular/common' import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' import { provideHttpClientTesting } from '@angular/common/http/testing' import { ComponentFixture, TestBed, fakeAsync, tick, } from '@angular/core/testing' import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { of, throwError } from 'rxjs' import { Group } from 'src/app/data/group' import { User } from 'src/app/data/user' 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 { UserService } from 'src/app/services/rest/user.service' import { SettingsService } from 'src/app/services/settings.service' import { ToastService } from 'src/app/services/toast.service' import * as navUtils from 'src/app/utils/navigation' 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 { UsersAndGroupsComponent } from './users-groups.component' 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' }, ] describe('UsersAndGroupsComponent', () => { let component: UsersAndGroupsComponent let fixture: ComponentFixture let settingsService: SettingsService let modalService: NgbModal let toastService: ToastService let userService: UserService let permissionsService: PermissionsService let groupService: GroupService beforeEach(() => { TestBed.configureTestingModule({ imports: [NgxBootstrapIconsModule.pick(allIcons)], providers: [ CustomDatePipe, DatePipe, PermissionsGuard, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting(), ], }).compileComponents() fixture = TestBed.createComponent(UsersAndGroupsComponent) settingsService = TestBed.inject(SettingsService) settingsService.currentUser = users[0] userService = TestBed.inject(UserService) modalService = TestBed.inject(NgbModal) toastService = TestBed.inject(ToastService) permissionsService = TestBed.inject(PermissionsService) jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) jest .spyOn(permissionsService, 'currentUserHasObjectPermissions') .mockReturnValue(true) jest .spyOn(permissionsService, 'currentUserOwnsObject') .mockReturnValue(true) groupService = TestBed.inject(GroupService) component = fixture.componentInstance fixture.detectChanges() }) function completeSetup(excludeService = null) { if (excludeService !== userService) { jest.spyOn(userService, 'listAll').mockReturnValue( of({ all: users.map((a) => a.id), count: users.length, results: (users as User[]).concat([]), }) ) } if (excludeService !== groupService) { jest.spyOn(groupService, 'listAll').mockReturnValue( of({ all: groups.map((r) => r.id), count: groups.length, results: (groups as Group[]).concat([]), }) ) } fixture = TestBed.createComponent(UsersAndGroupsComponent) component = fixture.componentInstance fixture.detectChanges() } it('should support edit / create user, show error if needed', () => { completeSetup() 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).toHaveBeenCalled() settingsService.currentUser = users[1] // simulate logged in as different user editDialog.succeeded.emit(users[0]) expect(toastInfoSpy).toHaveBeenCalledWith( `Saved user "${users[0].username}".` ) component.editUser() }) it('should support delete user, show error if needed', () => { completeSetup() 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).toHaveBeenCalled() deleteSpy.mockReturnValueOnce(of(true)) deleteDialog.confirm() expect(listAllSpy).toHaveBeenCalled() expect(toastInfoSpy).toHaveBeenCalledWith('Deleted user "user1"') }) it('should logout current user if password changed, after delay', fakeAsync(() => { completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) component.editUser(users[0]) const navSpy = jest .spyOn(navUtils, 'setLocationHref') .mockImplementation(() => {}) 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() tick(2600) expect(navSpy).toHaveBeenCalledWith( `${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/` ) })) it('should support edit / create group, show error if needed', () => { completeSetup() 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).toHaveBeenCalled() editDialog.succeeded.emit(groups[0]) expect(toastInfoSpy).toHaveBeenCalledWith( `Saved group "${groups[0].name}".` ) component.editGroup() }) it('should support delete group, show error if needed', () => { completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) component.deleteGroup(groups[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).toHaveBeenCalled() deleteSpy.mockReturnValueOnce(of(true)) deleteDialog.confirm() expect(listAllSpy).toHaveBeenCalled() expect(toastInfoSpy).toHaveBeenCalledWith('Deleted group "group1"') }) it('should get group name', () => { completeSetup() expect(component.getGroupName(1)).toEqual(groups[0].name) expect(component.getGroupName(11)).toEqual('') }) it('should show errors on load if load users failure', () => { const toastErrorSpy = jest.spyOn(toastService, 'showError') jest .spyOn(userService, 'listAll') .mockImplementation(() => throwError(() => new Error('failed to load users')) ) completeSetup(userService) fixture.detectChanges() expect(toastErrorSpy).toHaveBeenCalled() }) it('should show errors on load if load groups failure', () => { const toastErrorSpy = jest.spyOn(toastService, 'showError') jest .spyOn(groupService, 'listAll') .mockImplementation(() => throwError(() => new Error('failed to load groups')) ) completeSetup(groupService) fixture.detectChanges() expect(toastErrorSpy).toHaveBeenCalled() }) })