Skip to content

Instantly share code, notes, and snippets.

@scionwest
Created March 24, 2017 01:31
Show Gist options
  • Save scionwest/04b65dc81ae21429ab928e90e0973265 to your computer and use it in GitHub Desktop.
Save scionwest/04b65dc81ae21429ab928e90e0973265 to your computer and use it in GitHub Desktop.
Account creation and authentication components with spec file for unit-tests.
export enum AccountFormType {
SignIn = 1,
CreateAccount = 2
}
// Angular
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
import { Injectable } from '@angular/core';
// Custom Enum
import { AccountFormType } from './account-form-type.enum';
@Injectable()
// Factory responsible for creating the FormGroups used by the components facilitating account sign-in and account creation.
export class AccountFormFactory {
// Inject a FormBuilder into the factory constructor.
constructor(private formBuilder: FormBuilder) {}
// Creates a FormGroup representing a 'New Account' form.
public createAccountForm(): FormGroup {
return this.createForm(AccountFormType.CreateAccount);
}
// Creates a FormGroup representing a 'Sign-In' form.
public signInForm(): FormGroup {
return this.createForm(AccountFormType.SignIn);
}
// Conditionally creates a 'New Account' or 'Sign-In' form based on the FormType provided.
private createForm(formType: AccountFormType): FormGroup {
var form: FormGroup;
let emailRegex = '^[a-z0-9]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,15})$';
if (formType === AccountFormType.SignIn) {
form = this.formBuilder.group({
'emailAddress': new FormControl('', [Validators.required, Validators.pattern(emailRegex)]),
'password': new FormControl('', Validators.required),
'accountName': new FormControl('', Validators.required)
})
} else if (formType === AccountFormType.CreateAccount) {
form = this.formBuilder.group({
'emailAddress': new FormControl('', [Validators.required, Validators.pattern(emailRegex)]),
'password': new FormControl('', Validators.required),
'accountName': new FormControl('', Validators.required),
'passwordConfirmation': new FormControl('', Validators.required)
});
}
// Return the created FormGroup
return form;
}
}
export interface Authentication {
emailAddress: string;
password: string;
confirmPassword: string;
accountName: string;
providerTypeId: number;
}
import { Injectable, OnInit } from '@angular/core';
import { Http, URLSearchParams, Headers, RequestOptions, Response } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/mergeMap';
import { Authentication } from '../authentication.interface';
import { BearerToken } from './bearer-token';
@Injectable()
export class AuthenticationService implements OnInit {
private authTokenKey: string = 'auth_token';
private createAccountApi: string = 'Register/ProviderAccount';
private tokenApi: string = 'connect/token';
private access_token: string;
private accountHeader: Headers;
private tokenHeader: Headers;
private accountOptions: RequestOptions;
private tokenOptions: RequestOptions;
constructor(private http: Http) {
this.accountHeader = new Headers({
'Content-Type': 'application/json'
});
this.tokenHeader = new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
});
// var tokenHeader = new Headers();
// tokenHeader.append('Content-Type', 'application/json');
// tokenHeader.append('Authorization', 'Bearer ' + localStorage.getItem('id_token'));
this.accountOptions = new RequestOptions({ headers: this.accountHeader });
this.tokenOptions = new RequestOptions({ headers: this.tokenHeader });
}
get isAuthenticated(): boolean {
if (this.access_token) {
return true;
} else {
return false;
}
}
ngOnInit() {
this.access_token = localStorage.getItem(this.authTokenKey);
}
createAccount(authentication: Authentication): void {
authentication.providerTypeId = 1;
let data: any = JSON.stringify(authentication);
let data2 = new URLSearchParams();
data2.append('username', authentication.emailAddress);
data2.append('password', authentication.password);
// data2.append('provider', authentication.accountName);
data2.append('grant_type', 'password');
this.http
.post(this.createAccountApi, data, this.accountOptions)
.flatMap(response => {
return this.http.post(this.tokenApi, data2);
})
.map(response => response.json())
.catch(this.handleError)
.subscribe(this.handleResponse);
}
public logIn(authentication: Authentication): void {
let data = new URLSearchParams();
data.append('username', authentication.emailAddress);
data.append('password', authentication.password);
// data.append('provider', authentication.accountName);
data.append('grant_type', 'password');
this.http.post(this.tokenApi, data)
.catch(this.handleError)
.subscribe(this.handleResponse);
}
public logOut(): void {
this.access_token = null;
localStorage.removeItem(this.authTokenKey);
}
public refreshToken(): void {}
private handleResponse(response: any): void {
// let token: BearerToken = new BearerToken(response.access_token, response.expires_in, response.token_type);
localStorage.setItem(this.authTokenKey, response.access_token);
console.log(response);
}
private handleError(error: any): Promise<any> {
// TODO: Remove after debugging is done
console.error('An error occured', error);
return Promise.reject(error.message || error);
}
}
import { Guid } from '../guid';
export class BearerToken {
constructor(
public accessToken: string,
public expiresIn: number,
public tokenType: string,
public providerId: Guid,
public userId: Guid)
{ }
}
<form [formGroup]="createAccountForm" novalidate (ngSubmit)="onSubmit(createAccountForm)" >
<div>
<input type="email"
formControlName="emailAddress"
placeholder="Email Address" />
</div>
<div>
<input type="accountName"
formControlName="accountName"
placeholder="Provider Name" />
</div>
<div>
<input type="password"
formControlName="password"
placeholder="Password" />
</div>
<div>
<input type="password"
formControlName="passwordConfirmation"
placeholder="Confirm Password" />
</div>
<div>
<button type="submit" [disabled]="createAccountForm.invalid">Create</button>
<button type="button">Cancel</button>
</div>
</form>
import { Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { Authentication } from '../authentication.interface';
import { AuthenticationService } from './authentication.service';
import { AccountFormFactory } from './account-form.factory';
@Component({
selector: 'app-create-account',
templateUrl: './create-account.component.html'
})
export class CreateAccountComponent {
private authService: AuthenticationService;
public createAccountForm: FormGroup;
constructor(formFactory: AccountFormFactory, private authenticationService: AuthenticationService) {
this.authService = authenticationService;
// Create the FormGroup our template will bind to
this.createAccountForm = formFactory.createAccountForm();
}
onSubmit({ value, valid }: { value: Authentication, valid: boolean }) {
this.authService.createAccount(value);
}
}
<form [formGroup]="signInForm" novalidate (ngSubmit)="onSubmit(signInForm)">
<div>
<input type="email"
formControlName="emailAddress"
placeholder="Email Address" />
</div>
<div>
<input type="accountName"
formControlName="accountName"
placeholder="Provider Name" />
</div>
<div>
<input type="password"
formControlName="password"
placeholder="Password" />
</div>
<div>
<button type="submit" [disabled]="signInForm.invalid">Sign-in</button>
<button type="button">Cancel</button>
</div>
</form>
// Angular dependencies
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
// Custom dependencies
import { Authentication } from '../authentication.interface';
import { SignInComponent } from './sign-in.component';
import { AccountFormFactory } from './account-form.factory';
import { AuthenticationService } from './authentication.service';
// AuthenticationService Mock
let authenticationService = {
createAccount(user: Authentication): void {}
}
// Key/Value Pair lookups
let emailControlName: string = 'emailAddress';
let passwordControlName: string = 'password';
let accountNameControlName: string = 'accountName';
describe('Sign-In Component', () => {
// References for our component and the elements we are testing.
let component: SignInComponent;
let fixture: ComponentFixture<SignInComponent>;
let debugElement: DebugElement;
let htmlElement: HTMLElement;
// Initialize each test
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, FormsModule],
providers: [AccountFormFactory, {provide: AuthenticationService, useValue: authenticationService } ],
declarations: [ SignInComponent ],
});
fixture = TestBed.createComponent(SignInComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
htmlElement = debugElement.nativeElement;
});
it('form invalid when empty', () => {
// Assert
expect(component.signInForm.valid).toBeFalsy();
});
it('form is valid when filled-out proper', () => {
// Arrange
let emailControl = component.signInForm.controls[emailControlName];
let passwordControl = component.signInForm.controls[passwordControlName];
let accountNameControl = component.signInForm.controls[accountNameControlName];
// Act
emailControl.setValue('[email protected]');
passwordControl.setValue('1234_Foo');
accountNameControl.setValue('my account');
// Assert
expect(component.signInForm.valid).toBeTruthy();
})
it('email address required', () => {
// Arrange
let emailControl = component.signInForm.controls[emailControlName];
// Assert
expect(emailControl.valid).toBeFalsy();
});
it('reject invalid email address format', () => {
// Arrange
let emailControl = component.signInForm.controls[emailControlName];
// Act
emailControl.setValue('foo-bar.com');
let errors = emailControl.errors || {};
// Assert
expect(errors['pattern']).toBeTruthy();
});
it('accept proper email address format', () => {
// Arrange
let emailControl = component.signInForm.controls[emailControlName];
// Act
emailControl.setValue('[email protected]');
// Assert
let errors = emailControl.errors || {};
expect(errors['pattern']).toBeFalsy();
});
it('account name required', () => {
// Arrange
let accountNameControl = component.signInForm.controls[accountNameControlName];
// Assert
expect(accountNameControl.valid).toBeFalsy();
});
it('account name is valid', () => {
// Arrange
let accountNameControl = component.signInForm.controls[accountNameControlName];
// Act
accountNameControl.setValue('my account');
// Assert
expect(accountNameControl.valid).toBeTruthy();
});
it('password required', () => {
// Arrange
let passwordControl = component.signInForm.controls[passwordControlName];
// Assert
expect(passwordControl.valid).toBeFalsy();
});
it('password is valid', () => {
// Arrange
let passwordControl = component.signInForm.controls[passwordControlName];
// Act
passwordControl.setValue('123_Foo');
// Assert
expect(passwordControl.valid).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { AccountFormFactory } from './account-form.factory';
import { Authentication } from '../authentication.interface';
import { AuthenticationService } from './authentication.service';
@Component({
selector: 'app-sign-in',
templateUrl: './sign-in.component.html'
})
export class SignInComponent {
private authService: AuthenticationService;
public signInForm: FormGroup;
constructor(formFactory: AccountFormFactory, authenticationService: AuthenticationService) {
this.authService = authenticationService;
// Create the FormGroup our template will bind to
this.signInForm = formFactory.signInForm();
}
onSubmit({ value, valid }: { value: Authentication, valid: boolean }) {
this.authService.createAccount(value);
}
}
import { Authentication } from './authentication.interface';
export class User implements Authentication {
emailAddress: string;
password: string;
confirmPassword: string;
accountName: string;
providerTypeId: number;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment