add unit tests
This commit is contained in:
@@ -1,16 +1,40 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {of} from 'rxjs';
|
||||
import {DbService} from './db.service';
|
||||
import {ConfigService} from './config.service';
|
||||
|
||||
describe('ConfigService', () => {
|
||||
let service: ConfigService;
|
||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
|
||||
beforeEach(() => {
|
||||
void TestBed.configureTestingModule({});
|
||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc$']);
|
||||
dbServiceSpy.doc$.and.returnValue(of({copyright: 'CCLI'}) as never);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||
});
|
||||
|
||||
service = TestBed.inject(ConfigService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
void expect(service).toBeTruthy();
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should read the global config document once on creation', () => {
|
||||
expect(dbServiceSpy.doc$).toHaveBeenCalledTimes(1);
|
||||
expect(dbServiceSpy.doc$).toHaveBeenCalledWith('global/config');
|
||||
});
|
||||
|
||||
it('should expose the shared config stream via get$', done => {
|
||||
service.get$().subscribe(config => {
|
||||
expect(config).toEqual({copyright: 'CCLI'} as never);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve the current config via get()', async () => {
|
||||
await expectAsync(service.get()).toBeResolvedTo({copyright: 'CCLI'} as never);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,46 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {of} from 'rxjs';
|
||||
import {DbService} from './db.service';
|
||||
import {GlobalSettingsService} from './global-settings.service';
|
||||
|
||||
describe('GlobalSettingsService', () => {
|
||||
let service: GlobalSettingsService;
|
||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
let updateSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
void TestBed.configureTestingModule({});
|
||||
updateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc$', 'doc']);
|
||||
dbServiceSpy.doc$.and.returnValue(of({churchName: 'ICF'}) as never);
|
||||
dbServiceSpy.doc.and.returnValue({update: updateSpy} as never);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
providers: [{provide: DbService, useValue: dbServiceSpy}],
|
||||
});
|
||||
|
||||
service = TestBed.inject(GlobalSettingsService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
void expect(service).toBeTruthy();
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should read the static global settings document once on creation', () => {
|
||||
expect(dbServiceSpy.doc$).toHaveBeenCalledTimes(1);
|
||||
expect(dbServiceSpy.doc$).toHaveBeenCalledWith('global/static');
|
||||
});
|
||||
|
||||
it('should expose the shared settings stream via the getter', done => {
|
||||
service.get$.subscribe(settings => {
|
||||
expect(settings).toEqual({churchName: 'ICF'} as never);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should update the static global settings document', async () => {
|
||||
await service.set({churchName: 'New Name'} as never);
|
||||
|
||||
expect(dbServiceSpy.doc).toHaveBeenCalledWith('global/static');
|
||||
expect(updateSpy).toHaveBeenCalledWith({churchName: 'New Name'} as never);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
|
||||
import {of} from 'rxjs';
|
||||
import {UserService} from '../user.service';
|
||||
import {UserNameComponent} from './user-name.component';
|
||||
|
||||
describe('UserNameComponent', () => {
|
||||
let component: UserNameComponent;
|
||||
let fixture: ComponentFixture<UserNameComponent>;
|
||||
let userServiceSpy: jasmine.SpyObj<UserService>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
userServiceSpy = jasmine.createSpyObj<UserService>('UserService', ['getUserbyId$']);
|
||||
userServiceSpy.getUserbyId$.and.returnValue(of({name: 'Benjamin'} as never));
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
imports: [UserNameComponent],
|
||||
providers: [{provide: UserService, useValue: userServiceSpy}],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
@@ -19,6 +25,27 @@ describe('UserNameComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
void expect(component).toBeTruthy();
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should resolve the user name when the userId input changes', done => {
|
||||
component.userId = 'user-1';
|
||||
|
||||
component.name$?.subscribe(name => {
|
||||
expect(userServiceSpy.getUserbyId$).toHaveBeenCalledWith('user-1');
|
||||
expect(name).toBe('Benjamin');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should map missing users to null names', done => {
|
||||
userServiceSpy.getUserbyId$.and.returnValue(of(null));
|
||||
|
||||
component.userId = 'missing-user';
|
||||
|
||||
component.name$?.subscribe(name => {
|
||||
expect(name).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
124
src/app/services/user/user-session.service.spec.ts
Normal file
124
src/app/services/user/user-session.service.spec.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {Router} from '@angular/router';
|
||||
import {BehaviorSubject, firstValueFrom, of} from 'rxjs';
|
||||
import {Auth} from '@angular/fire/auth';
|
||||
import {DbService} from '../db.service';
|
||||
import {UserSessionService} from './user-session.service';
|
||||
|
||||
describe('UserSessionService', () => {
|
||||
let service: UserSessionService;
|
||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
let routerSpy: jasmine.SpyObj<Router>;
|
||||
let authStateSubject: BehaviorSubject<unknown>;
|
||||
let createAuthStateSpy: jasmine.Spy;
|
||||
let runInFirebaseContextSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
authStateSubject = new BehaviorSubject<unknown>(null);
|
||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['col$', 'doc$', 'doc']);
|
||||
routerSpy = jasmine.createSpyObj<Router>('Router', ['navigateByUrl']);
|
||||
dbServiceSpy.col$.and.returnValue(of([{id: 'user-1'}]) as never);
|
||||
dbServiceSpy.doc$.and.callFake((path: string) => {
|
||||
if (path === 'users/user-1') {
|
||||
return of({id: 'user-1', name: 'Benjamin', role: 'leader', chordMode: 'letters', songUsage: {}}) as never;
|
||||
}
|
||||
if (path === 'users/user-2') {
|
||||
return of({id: 'user-2', name: 'Paula', role: 'user', chordMode: 'letters', songUsage: {}}) as never;
|
||||
}
|
||||
|
||||
return of(null) as never;
|
||||
});
|
||||
dbServiceSpy.doc.and.returnValue({
|
||||
update: jasmine.createSpy('update').and.resolveTo(),
|
||||
set: jasmine.createSpy('set').and.resolveTo(),
|
||||
} as never);
|
||||
routerSpy.navigateByUrl.and.resolveTo(true);
|
||||
|
||||
createAuthStateSpy = spyOn<any>(UserSessionService.prototype, 'createAuthState$').and.returnValue(authStateSubject.asObservable() as never);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: DbService, useValue: dbServiceSpy},
|
||||
{provide: Router, useValue: routerSpy},
|
||||
{provide: Auth, useValue: {}},
|
||||
],
|
||||
});
|
||||
|
||||
service = TestBed.inject(UserSessionService);
|
||||
runInFirebaseContextSpy = spyOn<any>(service, 'runInFirebaseContext');
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
expect(createAuthStateSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should derive userId$ and loggedIn$ from authState', async () => {
|
||||
authStateSubject.next({uid: 'user-1'});
|
||||
|
||||
await expectAsync(firstValueFrom(service.userId$)).toBeResolvedTo('user-1');
|
||||
await expectAsync(firstValueFrom(service.loggedIn$())).toBeResolvedTo(true);
|
||||
});
|
||||
|
||||
it('should resolve the current user document from auth state', async () => {
|
||||
authStateSubject.next({uid: 'user-1'});
|
||||
|
||||
await expectAsync(firstValueFrom(service.user$)).toBeResolvedTo(
|
||||
jasmine.objectContaining({id: 'user-1', name: 'Benjamin'}) as never
|
||||
);
|
||||
});
|
||||
|
||||
it('should cache user lookups by id', async () => {
|
||||
const first$ = service.getUserbyId$('user-2');
|
||||
const second$ = service.getUserbyId$('user-2');
|
||||
|
||||
expect(first$).toBe(second$);
|
||||
await expectAsync(firstValueFrom(first$)).toBeResolvedTo(jasmine.objectContaining({id: 'user-2'}) as never);
|
||||
});
|
||||
|
||||
it('should login and initialize songUsage when missing', async () => {
|
||||
dbServiceSpy.doc$.and.callFake((path: string) => {
|
||||
if (path === 'users/user-1') {
|
||||
return of({id: 'user-1', name: 'Benjamin', role: 'leader', chordMode: 'letters'}) as never;
|
||||
}
|
||||
|
||||
return of(null) as never;
|
||||
});
|
||||
const updateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||
dbServiceSpy.doc.and.returnValue({update: updateSpy} as never);
|
||||
runInFirebaseContextSpy.and.resolveTo({user: {uid: 'user-1'}});
|
||||
|
||||
await expectAsync(service.login('mail', 'secret')).toBeResolvedTo('user-1');
|
||||
|
||||
expect(runInFirebaseContextSpy).toHaveBeenCalledTimes(1);
|
||||
expect(updateSpy).toHaveBeenCalledWith({songUsage: {}});
|
||||
});
|
||||
|
||||
it('should delegate logout and password reset to AngularFire auth APIs', async () => {
|
||||
runInFirebaseContextSpy.and.resolveTo();
|
||||
|
||||
await service.logout();
|
||||
await service.changePassword('mail@example.com');
|
||||
|
||||
expect(runInFirebaseContextSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should create a new user document and navigate afterwards', async () => {
|
||||
dbServiceSpy.doc$.and.callFake((path: string) => {
|
||||
if (path === 'users/user-3') {
|
||||
return of({id: 'user-3', name: 'New User', role: 'user', chordMode: 'onlyFirst', songUsage: {}}) as never;
|
||||
}
|
||||
|
||||
return of(null) as never;
|
||||
});
|
||||
const setSpy = jasmine.createSpy('set').and.resolveTo();
|
||||
dbServiceSpy.doc.and.returnValue({set: setSpy} as never);
|
||||
runInFirebaseContextSpy.and.resolveTo({user: {uid: 'user-3'}});
|
||||
|
||||
await service.createNewUser('mail@example.com', 'New User', 'secret');
|
||||
|
||||
expect(runInFirebaseContextSpy).toHaveBeenCalledTimes(1);
|
||||
expect(setSpy).toHaveBeenCalledWith({name: 'New User', chordMode: 'onlyFirst', songUsage: {}});
|
||||
expect(routerSpy.navigateByUrl).toHaveBeenCalledWith('/brand/new-user');
|
||||
});
|
||||
});
|
||||
76
src/app/services/user/user-song-usage.service.spec.ts
Normal file
76
src/app/services/user/user-song-usage.service.spec.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {of} from 'rxjs';
|
||||
import {DbService} from '../db.service';
|
||||
import {ShowDataService} from '../../modules/shows/services/show-data.service';
|
||||
import {ShowSongDataService} from '../../modules/shows/services/show-song-data.service';
|
||||
import {UserSessionService} from './user-session.service';
|
||||
import {UserSongUsageService} from './user-song-usage.service';
|
||||
|
||||
describe('UserSongUsageService', () => {
|
||||
let service: UserSongUsageService;
|
||||
let dbServiceSpy: jasmine.SpyObj<DbService>;
|
||||
let sessionSpy: jasmine.SpyObj<UserSessionService>;
|
||||
let showDataServiceSpy: jasmine.SpyObj<ShowDataService>;
|
||||
let showSongDataServiceSpy: jasmine.SpyObj<ShowSongDataService>;
|
||||
|
||||
beforeEach(() => {
|
||||
dbServiceSpy = jasmine.createSpyObj<DbService>('DbService', ['doc']);
|
||||
sessionSpy = jasmine.createSpyObj<UserSessionService>('UserSessionService', ['update$'], {
|
||||
user$: of({id: 'user-1', role: 'admin', songUsage: {}}) as never,
|
||||
users$: of([{id: 'user-1'}, {id: 'user-2'}]) as never,
|
||||
});
|
||||
showDataServiceSpy = jasmine.createSpyObj<ShowDataService>('ShowDataService', ['listRaw$']);
|
||||
showSongDataServiceSpy = jasmine.createSpyObj<ShowSongDataService>('ShowSongDataService', ['list$']);
|
||||
|
||||
sessionSpy.update$.and.resolveTo();
|
||||
showDataServiceSpy.listRaw$.and.returnValue(of([{id: 'show-1', owner: 'user-1'}, {id: 'show-2', owner: 'user-2'}] as never));
|
||||
showSongDataServiceSpy.list$.and.callFake((showId: string) =>
|
||||
of(showId === 'show-1' ? ([{songId: 'song-1'}, {songId: 'song-1'}, {songId: 'song-2'}] as never) : ([{songId: 'song-3'}] as never))
|
||||
);
|
||||
dbServiceSpy.doc.and.returnValue({update: jasmine.createSpy('update').and.resolveTo()} as never);
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: DbService, useValue: dbServiceSpy},
|
||||
{provide: UserSessionService, useValue: sessionSpy},
|
||||
{provide: ShowDataService, useValue: showDataServiceSpy},
|
||||
{provide: ShowSongDataService, useValue: showSongDataServiceSpy},
|
||||
],
|
||||
});
|
||||
|
||||
service = TestBed.inject(UserSongUsageService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should increment and decrement song usage for the current user', async () => {
|
||||
const updateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||
dbServiceSpy.doc.and.returnValue({update: updateSpy} as never);
|
||||
|
||||
await service.incSongCount('song-1');
|
||||
await service.decSongCount('song-2');
|
||||
|
||||
expect(dbServiceSpy.doc).toHaveBeenCalledWith('users/user-1');
|
||||
expect(updateSpy.calls.argsFor(0)[0]).toEqual({'songUsage.song-1': jasmine.anything()});
|
||||
expect(updateSpy.calls.argsFor(1)[0]).toEqual({'songUsage.song-2': jasmine.anything()});
|
||||
});
|
||||
|
||||
it('should rebuild song usage for all users based on owned show songs', async () => {
|
||||
await expectAsync(service.rebuildSongUsage()).toBeResolvedTo({
|
||||
usersProcessed: 2,
|
||||
showsProcessed: 2,
|
||||
showSongsProcessed: 4,
|
||||
});
|
||||
|
||||
expect(sessionSpy.update$).toHaveBeenCalledWith('user-1', {songUsage: {'song-1': 2, 'song-2': 1}});
|
||||
expect(sessionSpy.update$).toHaveBeenCalledWith('user-2', {songUsage: {'song-3': 1}});
|
||||
});
|
||||
|
||||
it('should reject song usage rebuilds for non-admin users', async () => {
|
||||
Object.defineProperty(sessionSpy, 'user$', {value: of({id: 'user-1', role: 'leader'})});
|
||||
|
||||
await expectAsync(service.rebuildSongUsage()).toBeRejectedWithError('Admin role required to rebuild songUsage.');
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,108 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {of} from 'rxjs';
|
||||
import {UserService} from './user.service';
|
||||
import {UserSessionService} from './user-session.service';
|
||||
import {UserSongUsageService} from './user-song-usage.service';
|
||||
|
||||
describe('UserService', () => {
|
||||
beforeEach(() => void TestBed.configureTestingModule({}));
|
||||
let service: UserService;
|
||||
let sessionSpy: jasmine.SpyObj<UserSessionService>;
|
||||
let songUsageSpy: jasmine.SpyObj<UserSongUsageService>;
|
||||
|
||||
beforeEach(() => {
|
||||
sessionSpy = jasmine.createSpyObj<UserSessionService>(
|
||||
'UserSessionService',
|
||||
['currentUser', 'getUserbyId', 'getUserbyId$', 'login', 'loggedIn$', 'list$', 'logout', 'update$', 'changePassword', 'createNewUser'],
|
||||
{
|
||||
users$: of([{id: 'user-1'}]) as never,
|
||||
userId$: of('user-1'),
|
||||
user$: of({id: 'user-1'}) as never,
|
||||
}
|
||||
);
|
||||
songUsageSpy = jasmine.createSpyObj<UserSongUsageService>('UserSongUsageService', ['incSongCount', 'decSongCount', 'rebuildSongUsage']);
|
||||
|
||||
sessionSpy.currentUser.and.resolveTo({id: 'user-1'} as never);
|
||||
sessionSpy.getUserbyId.and.resolveTo({id: 'user-2'} as never);
|
||||
sessionSpy.getUserbyId$.and.returnValue(of({id: 'user-2'}) as never);
|
||||
sessionSpy.login.and.resolveTo('user-1');
|
||||
sessionSpy.loggedIn$.and.returnValue(of(true));
|
||||
sessionSpy.list$.and.returnValue(of([{id: 'user-1'}]) as never);
|
||||
sessionSpy.logout.and.resolveTo();
|
||||
sessionSpy.update$.and.resolveTo();
|
||||
sessionSpy.changePassword.and.resolveTo();
|
||||
sessionSpy.createNewUser.and.resolveTo();
|
||||
songUsageSpy.incSongCount.and.resolveTo();
|
||||
songUsageSpy.decSongCount.and.resolveTo();
|
||||
songUsageSpy.rebuildSongUsage.and.resolveTo({usersProcessed: 1, showsProcessed: 2, showSongsProcessed: 3});
|
||||
|
||||
void TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: UserSessionService, useValue: sessionSpy},
|
||||
{provide: UserSongUsageService, useValue: songUsageSpy},
|
||||
],
|
||||
});
|
||||
|
||||
service = TestBed.inject(UserService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
const service: UserService = TestBed.inject(UserService);
|
||||
void expect(service).toBeTruthy();
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should expose the session streams directly', done => {
|
||||
service.userId$.subscribe(userId => {
|
||||
expect(userId).toBe('user-1');
|
||||
service.user$.subscribe(user => {
|
||||
expect(user).toEqual({id: 'user-1'} as never);
|
||||
service.users$.subscribe(users => {
|
||||
expect(users).toEqual([{id: 'user-1'}] as never);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should delegate session operations to UserSessionService', async () => {
|
||||
await expectAsync(service.currentUser()).toBeResolvedTo({id: 'user-1'} as never);
|
||||
await expectAsync(service.getUserbyId('user-2')).toBeResolvedTo({id: 'user-2'} as never);
|
||||
await expectAsync(service.login('mail', 'secret')).toBeResolvedTo('user-1');
|
||||
await service.logout();
|
||||
await service.update$('user-1', {name: 'Benjamin'} as never);
|
||||
await service.changePassword('mail');
|
||||
await service.createNewUser('mail', 'Benjamin', 'secret');
|
||||
|
||||
expect(sessionSpy.currentUser).toHaveBeenCalled();
|
||||
expect(sessionSpy.getUserbyId).toHaveBeenCalledWith('user-2');
|
||||
expect(sessionSpy.login).toHaveBeenCalledWith('mail', 'secret');
|
||||
expect(sessionSpy.logout).toHaveBeenCalled();
|
||||
expect(sessionSpy.update$).toHaveBeenCalledWith('user-1', {name: 'Benjamin'} as never);
|
||||
expect(sessionSpy.changePassword).toHaveBeenCalledWith('mail');
|
||||
expect(sessionSpy.createNewUser).toHaveBeenCalledWith('mail', 'Benjamin', 'secret');
|
||||
});
|
||||
|
||||
it('should delegate user lookup and loggedIn/list streams to UserSessionService', done => {
|
||||
service.getUserbyId$('user-2').subscribe(user => {
|
||||
expect(user).toEqual({id: 'user-2'} as never);
|
||||
service.loggedIn$().subscribe(loggedIn => {
|
||||
expect(loggedIn).toBeTrue();
|
||||
service.list$().subscribe(users => {
|
||||
expect(users).toEqual([{id: 'user-1'}] as never);
|
||||
expect(sessionSpy.getUserbyId$).toHaveBeenCalledWith('user-2');
|
||||
expect(sessionSpy.loggedIn$).toHaveBeenCalled();
|
||||
expect(sessionSpy.list$).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should delegate song usage operations to UserSongUsageService', async () => {
|
||||
await service.incSongCount('song-1');
|
||||
await service.decSongCount('song-2');
|
||||
await expectAsync(service.rebuildSongUsage()).toBeResolvedTo({usersProcessed: 1, showsProcessed: 2, showSongsProcessed: 3});
|
||||
|
||||
expect(songUsageSpy.incSongCount).toHaveBeenCalledWith('song-1');
|
||||
expect(songUsageSpy.decSongCount).toHaveBeenCalledWith('song-2');
|
||||
expect(songUsageSpy.rebuildSongUsage).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user