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