-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #461 from PermanentOrg/load-tags-on-login
Load archives/tags properly on login
- Loading branch information
Showing
4 changed files
with
385 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,104 +1,334 @@ | ||
/* @format */ | ||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; | ||
import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; | ||
import { RouterTestingModule } from '@angular/router/testing'; | ||
import { HttpClientTestingModule } from '@angular/common/http/testing'; | ||
import { NgModule } from '@angular/core'; | ||
import { ActivatedRoute, Router } from '@angular/router'; | ||
import { CookieService } from 'ngx-cookie-service'; | ||
|
||
import { Shallow } from 'shallow-render'; | ||
import { LoginComponent } from '@auth/components/login/login.component'; | ||
import { LogoComponent } from '@auth/components/logo/logo.component'; | ||
import { FormInputComponent } from '@shared/components/form-input/form-input.component'; | ||
import { MessageService } from '@shared/services/message/message.service'; | ||
import { TEST_DATA } from '@core/core.module.spec'; | ||
import { AccountService } from '@shared/services/account/account.service'; | ||
import { FORM_ERROR_MESSAGES } from '@shared/utilities/forms'; | ||
import { AuthResponse } from '@shared/services/api/auth.repo'; | ||
import { TestBed } from '@angular/core/testing'; | ||
import { DeviceService } from '@shared/services/device/device.service'; | ||
import { ArchiveVO } from '@models/index'; | ||
|
||
const testEmail = 'unittest@example.com'; | ||
|
||
@NgModule() | ||
class DummyModule {} | ||
|
||
class MockAccountService { | ||
public switchedToDefaultArchive: boolean = false; | ||
public archives: ArchiveVO[] = []; | ||
public mfaResponse: boolean = false; | ||
public failResponse: boolean = false; | ||
public responseMessage: string = ''; | ||
public async logIn( | ||
_email: string, | ||
_password: string, | ||
_rememberMe: string, | ||
_keepLoggedIn: string, | ||
) { | ||
const resp = new AuthResponse({ | ||
isSuccessful: !this.failResponse && !this.mfaResponse, | ||
}); | ||
resp.setMessage([this.responseMessage]); | ||
if (this.failResponse) { | ||
throw resp; | ||
} else { | ||
return resp; | ||
} | ||
} | ||
|
||
public async refreshArchives() { | ||
return this.archives; | ||
} | ||
|
||
public getArchives() { | ||
return this.archives; | ||
} | ||
|
||
public async switchToDefaultArchive() { | ||
this.switchedToDefaultArchive = true; | ||
} | ||
} | ||
|
||
class MockActivatedRoute { | ||
public snapshot: { queryParams: Record<string, string> } = { | ||
queryParams: {}, | ||
}; | ||
} | ||
|
||
class MockRouter { | ||
public navigatedRoute: string[] = []; | ||
public async navigate(path: string[]) { | ||
this.navigatedRoute = path; | ||
} | ||
} | ||
|
||
class MockMessageService { | ||
showMessage(_: string) {} | ||
} | ||
|
||
class LoginTestingHarness { | ||
private component: LoginComponent; | ||
constructor( | ||
private account: MockAccountService, | ||
private route: MockActivatedRoute, | ||
) {} | ||
|
||
public setComponent(instance: LoginComponent) { | ||
this.component = instance; | ||
} | ||
|
||
public setupLoginError(): void { | ||
this.account.failResponse = true; | ||
this.account.responseMessage = 'unknown error'; | ||
} | ||
|
||
public setupIncorrectLogin(): void { | ||
this.account.failResponse = true; | ||
this.account.responseMessage = 'warning.signin.unknown'; | ||
} | ||
|
||
public setupMfa(): void { | ||
this.account.mfaResponse = true; | ||
this.account.responseMessage = 'warning.auth.mfaToken'; | ||
} | ||
|
||
public setupVerify(): void { | ||
this.account.mfaResponse = true; | ||
this.account.responseMessage = 'status.auth.need'; | ||
} | ||
|
||
public setupShareByUrl(param: string): void { | ||
this.route.snapshot.queryParams.shareByUrl = param; | ||
} | ||
|
||
public setupTimelineCta(): void { | ||
this.route.snapshot.queryParams.cta = 'timeline'; | ||
} | ||
|
||
public setupNormalLogin(): void { | ||
this.account.archives = [new ArchiveVO({ archiveId: 1 })]; | ||
} | ||
|
||
public setupOnboarding(): void { | ||
this.account.archives = []; | ||
} | ||
|
||
public async testLogin() { | ||
this.component.loginForm.patchValue({ | ||
email: testEmail, | ||
password: 'testpassword', | ||
}); | ||
|
||
await this.component.onSubmit({ | ||
email: testEmail, | ||
password: 'testpassword', | ||
rememberMe: true, | ||
keepLoggedIn: true, | ||
}); | ||
} | ||
|
||
public getMessageSpy(inject: typeof TestBed.inject) { | ||
return spyOn(inject(MessageService), 'showMessage').and.callThrough(); | ||
} | ||
|
||
public hasPasswordBeenCleared(): boolean { | ||
return this.component.loginForm.value.password.length == 0; | ||
} | ||
} | ||
|
||
describe('LoginComponent', () => { | ||
let component: LoginComponent; | ||
let fixture: ComponentFixture<LoginComponent>; | ||
|
||
beforeEach(waitForAsync(() => { | ||
TestBed.configureTestingModule({ | ||
declarations: [LogoComponent, LoginComponent, FormInputComponent], | ||
imports: [ | ||
FormsModule, | ||
ReactiveFormsModule, | ||
HttpClientTestingModule, | ||
RouterTestingModule, | ||
], | ||
providers: [CookieService, MessageService, AccountService], | ||
}).compileComponents(); | ||
})); | ||
let shallow: Shallow<LoginComponent>; | ||
let cookieService: Map<string, string>; | ||
let accountService: MockAccountService; | ||
let activatedRoute: MockActivatedRoute; | ||
let router: MockRouter; | ||
let harness: LoginTestingHarness; | ||
|
||
beforeEach(() => { | ||
const cookieService = TestBed.get(CookieService) as CookieService; | ||
cookieService.set('rememberMe', TEST_DATA.account.primaryEmail); | ||
fixture = TestBed.createComponent(LoginComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
accountService = new MockAccountService(); | ||
activatedRoute = new MockActivatedRoute(); | ||
router = new MockRouter(); | ||
cookieService = new Map<string, string>(); | ||
cookieService.set('rememberMe', testEmail); | ||
harness = new LoginTestingHarness(accountService, activatedRoute); | ||
shallow = new Shallow(LoginComponent, DummyModule).provideMock( | ||
{ | ||
provide: AccountService, | ||
useValue: accountService, | ||
}, | ||
{ provide: ActivatedRoute, useValue: activatedRoute }, | ||
{ provide: CookieService, useValue: cookieService }, | ||
{ provide: MessageService, useClass: MockMessageService }, | ||
{ provide: Router, useValue: router }, | ||
{ | ||
provide: DeviceService, | ||
useValue: { | ||
isMobile() { | ||
return true; | ||
}, | ||
}, | ||
}, | ||
); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
it('should create', async () => { | ||
const { instance } = await shallow.render(); | ||
|
||
expect(instance).toBeTruthy(); | ||
}); | ||
|
||
it('should autofill with the email from cookies', () => { | ||
expect(component.loginForm.value.email).toEqual( | ||
TEST_DATA.account.primaryEmail, | ||
); | ||
it('should autofill with the email from cookies', async () => { | ||
const { instance } = await shallow.render(); | ||
|
||
expect(instance.loginForm.value.email).toEqual(testEmail); | ||
}); | ||
|
||
it('should set error for missing email', () => { | ||
component.loginForm.get('email').markAsTouched(); | ||
component.loginForm.patchValue({ | ||
it('should set error for missing email', async () => { | ||
const { instance } = await shallow.render(); | ||
instance.loginForm.get('email').markAsTouched(); | ||
instance.loginForm.patchValue({ | ||
email: '', | ||
password: TEST_DATA.user.password, | ||
}); | ||
|
||
expect(component.loginForm.invalid).toBeTruthy(); | ||
expect(component.loginForm.get('email').errors.required).toBeTruthy(); | ||
expect(instance.loginForm.invalid).toBeTruthy(); | ||
expect(instance.loginForm.get('email').errors.required).toBeTruthy(); | ||
}); | ||
|
||
it('should set error for invalid email', () => { | ||
component.loginForm.get('email').markAsTouched(); | ||
component.loginForm.patchValue({ | ||
it('should set error for invalid email', async () => { | ||
const { instance } = await shallow.render(); | ||
instance.loginForm.get('email').markAsTouched(); | ||
instance.loginForm.patchValue({ | ||
email: 'lasld;f;aslkj', | ||
password: TEST_DATA.user.password, | ||
}); | ||
|
||
expect(component.loginForm.invalid).toBeTruthy(); | ||
expect(component.loginForm.get('email').errors.email).toBeTruthy(); | ||
expect(instance.loginForm.invalid).toBeTruthy(); | ||
expect(instance.loginForm.get('email').errors.email).toBeTruthy(); | ||
}); | ||
|
||
it('should set error for missing password', () => { | ||
component.loginForm.get('password').markAsTouched(); | ||
component.loginForm.patchValue({ | ||
it('should set error for missing password', async () => { | ||
const { instance } = await shallow.render(); | ||
instance.loginForm.get('password').markAsTouched(); | ||
instance.loginForm.patchValue({ | ||
email: TEST_DATA.user.email, | ||
password: '', | ||
}); | ||
|
||
expect(component.loginForm.invalid).toBeTruthy(); | ||
expect(component.loginForm.get('password').errors.required).toBeTruthy(); | ||
expect(instance.loginForm.invalid).toBeTruthy(); | ||
expect(instance.loginForm.get('password').errors.required).toBeTruthy(); | ||
}); | ||
|
||
it('should set error for too short password', () => { | ||
component.loginForm.get('password').markAsTouched(); | ||
component.loginForm.patchValue({ | ||
it('should set error for too short password', async () => { | ||
const { instance } = await shallow.render(); | ||
instance.loginForm.get('password').markAsTouched(); | ||
instance.loginForm.patchValue({ | ||
email: TEST_DATA.user.email, | ||
password: 'short', | ||
}); | ||
|
||
expect(component.loginForm.invalid).toBeTruthy(); | ||
expect(component.loginForm.get('password').errors.minlength).toBeTruthy(); | ||
expect(instance.loginForm.invalid).toBeTruthy(); | ||
expect(instance.loginForm.get('password').errors.minlength).toBeTruthy(); | ||
}); | ||
|
||
it('should have no errors when email and password valid', () => { | ||
component.loginForm.markAsTouched(); | ||
component.loginForm.patchValue({ | ||
it('should have no errors when email and password valid', async () => { | ||
const { instance } = await shallow.render(); | ||
instance.loginForm.markAsTouched(); | ||
instance.loginForm.patchValue({ | ||
email: TEST_DATA.user.email, | ||
password: TEST_DATA.user.password, | ||
}); | ||
|
||
expect(component.loginForm.valid).toBeTruthy(); | ||
expect(instance.loginForm.valid).toBeTruthy(); | ||
}); | ||
|
||
it('should log in the user if they have archives', async () => { | ||
const { instance } = await shallow.render(); | ||
|
||
harness.setComponent(instance); | ||
harness.setupNormalLogin(); | ||
await harness.testLogin(); | ||
|
||
expect(router.navigatedRoute).toContain('/'); | ||
expect(accountService.switchedToDefaultArchive).toBeTrue(); | ||
}); | ||
|
||
it('should redirect to onboarding if the user has no archives', async () => { | ||
const { instance } = await shallow.render(); | ||
|
||
harness.setComponent(instance); | ||
harness.setupOnboarding(); | ||
await harness.testLogin(); | ||
|
||
expect(router.navigatedRoute.join('/')).toContain('onboarding'); | ||
}); | ||
|
||
it('should redirect to public if the user is coming from timeline view', async () => { | ||
const { instance } = await shallow.render(); | ||
|
||
harness.setComponent(instance); | ||
harness.setupTimelineCta(); | ||
await harness.testLogin(); | ||
|
||
expect(router.navigatedRoute).toContain('/public'); | ||
}); | ||
|
||
it('should redirect to a sharebyurl if the param is set', async () => { | ||
const { instance } = await shallow.render(); | ||
|
||
harness.setComponent(instance); | ||
harness.setupShareByUrl('test-1234'); | ||
await harness.testLogin(); | ||
|
||
expect(router.navigatedRoute).toContain('/share'); | ||
expect(router.navigatedRoute).toContain('test-1234'); | ||
}); | ||
|
||
it('should redirect to Verify page if user needs verification', async () => { | ||
const { instance } = await shallow.render(); | ||
|
||
harness.setComponent(instance); | ||
harness.setupVerify(); | ||
await harness.testLogin(); | ||
|
||
expect(router.navigatedRoute).toContain('verify'); | ||
}); | ||
|
||
it('should redirect to MFA page if user needs MFA', async () => { | ||
const { instance } = await shallow.render(); | ||
|
||
harness.setComponent(instance); | ||
harness.setupMfa(); | ||
await harness.testLogin(); | ||
|
||
expect(router.navigatedRoute).toContain('mfa'); | ||
}); | ||
|
||
it('should show an error message in case of login failure', async () => { | ||
const { inject, instance } = await shallow.render(); | ||
|
||
harness.setComponent(instance); | ||
harness.setupLoginError(); | ||
const messageSpy = harness.getMessageSpy(inject); | ||
await harness.testLogin(); | ||
|
||
expect(messageSpy).toHaveBeenCalled(); | ||
expect(harness.hasPasswordBeenCleared()).toBeFalse(); | ||
}); | ||
|
||
it('should show an error message in case of wrong username/password', async () => { | ||
const { inject, instance } = await shallow.render(); | ||
|
||
harness.setComponent(instance); | ||
harness.setupIncorrectLogin(); | ||
const messageSpy = harness.getMessageSpy(inject); | ||
await harness.testLogin(); | ||
|
||
expect(messageSpy).toHaveBeenCalled(); | ||
expect(harness.hasPasswordBeenCleared()).toBeTrue(); | ||
}); | ||
}); |
Oops, something went wrong.