mirror of https://github.com/Squidex/squidex.git
21 changed files with 442 additions and 133 deletions
@ -0,0 +1,73 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { IMock, Mock } from 'typemoq'; |
|||
import { Observable } from 'rxjs'; |
|||
|
|||
import { AppsStoreService } from 'shared'; |
|||
|
|||
import { AppMustExistGuard } from './app-must-exist.guard'; |
|||
import { RouterMockup } from './router-mockup'; |
|||
|
|||
describe('AppMustExistGuard', () => { |
|||
let appsStore: IMock<AppsStoreService>; |
|||
|
|||
beforeEach(() => { |
|||
appsStore = Mock.ofType(AppsStoreService); |
|||
}); |
|||
|
|||
it('should navigate to 404 page if app is not found', (done) => { |
|||
appsStore.setup(x => x.selectApp('my-app')) |
|||
.returns(() => Observable.of(false)); |
|||
const router = new RouterMockup(); |
|||
const route = <any> { params: { appName: 'my-app' } }; |
|||
|
|||
const guard = new AppMustExistGuard(appsStore.object, <any>router); |
|||
|
|||
guard.canActivate(route, <any>{}) |
|||
.subscribe(result => { |
|||
expect(result).toBeFalsy(); |
|||
expect(router.lastNavigation).toEqual(['/404']); |
|||
|
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
it('should navigate to 404 page if app loading fails', (done) => { |
|||
appsStore.setup(x => x.selectApp('my-app')) |
|||
.returns(() => Observable.throw('error')); |
|||
const router = new RouterMockup(); |
|||
const route = <any> { params: { appName: 'my-app' } }; |
|||
|
|||
const guard = new AppMustExistGuard(appsStore.object, <any>router); |
|||
|
|||
guard.canActivate(route, <any>{}) |
|||
.subscribe(result => { |
|||
expect(result).toBeFalsy(); |
|||
expect(router.lastNavigation).toEqual(['/404']); |
|||
|
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
it('should return true if app is found', (done) => { |
|||
appsStore.setup(x => x.selectApp('my-app')) |
|||
.returns(() => Observable.of(true)); |
|||
const router = new RouterMockup(); |
|||
const route = <any> { params: { appName: 'my-app' } }; |
|||
|
|||
const guard = new AppMustExistGuard(appsStore.object, <any>router); |
|||
|
|||
guard.canActivate(route, <any>{}) |
|||
.subscribe(result => { |
|||
expect(result).toBeTruthy(); |
|||
expect(router.lastNavigation).toBeUndefined(); |
|||
|
|||
done(); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,40 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; |
|||
import { Observable } from 'rxjs'; |
|||
|
|||
import { AppsStoreService } from './../services/apps-store.service'; |
|||
|
|||
@Injectable() |
|||
export class AppMustExistGuard implements CanActivate { |
|||
constructor( |
|||
private readonly appsStore: AppsStoreService, |
|||
private readonly router: Router |
|||
) { |
|||
} |
|||
|
|||
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { |
|||
const appName = route.params['appName']; |
|||
|
|||
const result = |
|||
this.appsStore.selectApp(appName) |
|||
.do(dto => { |
|||
if (!dto) { |
|||
this.router.navigate(['/404']); |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
this.router.navigate(['/404']); |
|||
|
|||
return Observable.of(false); |
|||
}); |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
@ -1,45 +0,0 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; |
|||
import { Observable } from 'rxjs'; |
|||
|
|||
import { allParams } from 'framework'; |
|||
|
|||
import { AppDto, AppsService } from './../services/apps.service'; |
|||
|
|||
@Injectable() |
|||
export class ResolveAppGuard implements Resolve<AppDto> { |
|||
constructor( |
|||
private readonly appsService: AppsService, |
|||
private readonly router: Router |
|||
) { |
|||
} |
|||
|
|||
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<AppDto | null> { |
|||
const params = allParams(route); |
|||
|
|||
const appName = params['appName']; |
|||
|
|||
if (!appName) { |
|||
throw 'Route must contain app name.'; |
|||
} |
|||
|
|||
return this.appsService.getApps().first().map(x => x.find(a => a.name === appName)) |
|||
.do(dto => { |
|||
if (!dto) { |
|||
this.router.navigate(['/404']); |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
this.router.navigate(['/404']); |
|||
|
|||
return Observable.of(null); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router'; |
|||
|
|||
import { AppsStoreService } from './../services/apps-store.service'; |
|||
|
|||
@Injectable() |
|||
export class UnsetAppGuard implements CanActivate { |
|||
constructor( |
|||
private readonly appsStore: AppsStoreService |
|||
) { |
|||
} |
|||
|
|||
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { |
|||
this.appsStore.selectApp(null); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Observable } from 'rxjs'; |
|||
import { It, IMock, Mock, Times } from 'typemoq'; |
|||
|
|||
import { |
|||
AppDto, |
|||
AppsService, |
|||
AppsStoreService, |
|||
CreateAppDto, |
|||
DateTime |
|||
} from './../'; |
|||
|
|||
describe('AppsStoreService', () => { |
|||
const now = DateTime.now(); |
|||
|
|||
const oldApps = [ |
|||
new AppDto('id', 'old-name', 'Owner', now, now, 'Free', 'Plan'), |
|||
new AppDto('id', 'old-name', 'Owner', now, now, 'Free', 'Plan') |
|||
]; |
|||
const newApp = new AppDto('id', 'new-name', 'Owner', now, now, 'Free', 'Plan'); |
|||
|
|||
let appsService: IMock<AppsService>; |
|||
|
|||
beforeEach(() => { |
|||
appsService = Mock.ofType(AppsService); |
|||
}); |
|||
|
|||
it('should load automatically', () => { |
|||
appsService.setup(x => x.getApps()) |
|||
.returns(() => Observable.of(oldApps)) |
|||
.verifiable(Times.once()); |
|||
|
|||
const store = new AppsStoreService(appsService.object); |
|||
|
|||
let result1: AppDto[] | null = null; |
|||
let result2: AppDto[] | null = null; |
|||
|
|||
store.apps.subscribe(x => { |
|||
result1 = x; |
|||
}).unsubscribe(); |
|||
|
|||
store.apps.subscribe(x => { |
|||
result2 = x; |
|||
}).unsubscribe(); |
|||
|
|||
expect(result1).toEqual(oldApps); |
|||
expect(result2).toEqual(oldApps); |
|||
|
|||
appsService.verifyAll(); |
|||
}); |
|||
|
|||
it('should add app to cache when created', () => { |
|||
appsService.setup(x => x.getApps()) |
|||
.returns(() => Observable.of(oldApps)) |
|||
.verifiable(Times.once()); |
|||
|
|||
appsService.setup(x => x.postApp(It.isAny())) |
|||
.returns(() => Observable.of(newApp)) |
|||
.verifiable(Times.once()); |
|||
|
|||
const store = new AppsStoreService(appsService.object); |
|||
|
|||
let result1: AppDto[] | null = null; |
|||
let result2: AppDto[] | null = null; |
|||
|
|||
store.apps.subscribe(x => { |
|||
result1 = x; |
|||
}).unsubscribe(); |
|||
|
|||
store.createApp(new CreateAppDto('new-name'), now).subscribe(); |
|||
|
|||
store.apps.subscribe(x => { |
|||
result2 = x; |
|||
}).unsubscribe(); |
|||
|
|||
expect(result1).toEqual(oldApps); |
|||
expect(JSON.stringify(result2)).toEqual(JSON.stringify(oldApps.concat([newApp]))); |
|||
|
|||
appsService.verifyAll(); |
|||
}); |
|||
|
|||
it('should select app', (done) => { |
|||
appsService.setup(x => x.getApps()) |
|||
.returns(() => Observable.of(oldApps)) |
|||
.verifiable(Times.once()); |
|||
|
|||
const store = new AppsStoreService(appsService.object); |
|||
|
|||
store.selectApp('old-name').subscribe(isSelected => { |
|||
expect(isSelected).toBeTruthy(); |
|||
|
|||
appsService.verifyAll(); |
|||
|
|||
done(); |
|||
}, err => { |
|||
expect(err).toBeNull(); |
|||
|
|||
done(); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,70 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { BehaviorSubject, Observable, Observer, ReplaySubject } from 'rxjs'; |
|||
|
|||
import { DateTime } from 'framework'; |
|||
|
|||
import { |
|||
AppDto, |
|||
AppsService, |
|||
CreateAppDto |
|||
} from './apps.service'; |
|||
|
|||
@Injectable() |
|||
export class AppsStoreService { |
|||
private readonly apps$ = new ReplaySubject<AppDto[]>(1); |
|||
private readonly app$ = new BehaviorSubject<AppDto | null>(null); |
|||
|
|||
public get apps(): Observable<AppDto[]> { |
|||
return this.apps$; |
|||
} |
|||
|
|||
public get selectedApp(): Observable<AppDto | null> { |
|||
return this.app$; |
|||
} |
|||
|
|||
constructor( |
|||
private readonly appsService: AppsService |
|||
) { |
|||
if (!appsService) { |
|||
return; |
|||
} |
|||
|
|||
this.appsService.getApps() |
|||
.subscribe(apps => { |
|||
this.apps$.next(apps); |
|||
}, error => { |
|||
this.apps$.next([]); |
|||
}); |
|||
} |
|||
|
|||
public selectApp(name: string | null): Observable<boolean> { |
|||
return Observable.create((observer: Observer<boolean>) => { |
|||
this.apps$.subscribe(apps => { |
|||
const app = apps.find(x => x.name === name) || null; |
|||
|
|||
this.app$.next(app); |
|||
|
|||
observer.next(app !== null); |
|||
observer.complete(); |
|||
}, error => { |
|||
observer.error(error); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
public createApp(dto: CreateAppDto, now?: DateTime): Observable<AppDto> { |
|||
return this.appsService.postApp(dto) |
|||
.do(app => { |
|||
this.apps$.first().subscribe(apps => { |
|||
this.apps$.next(apps.concat([app])); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue