I am trying to set up an integration test which will grab some data from a backend API service using ngrx/data entities.
I have this StackBlitz set up: https://stackblitz.com/edit/ngrxdata-testing-not-working-pxfmkb?file=src/main.ts
It should run tests on startup - there are no expectations in my test cases, however I am looking in the console logs and expecting it to show the log in ClientDataService (/src/app/data/client/client-data.service.ts), that is:
console.log('never goes here :(');
In the integration test (data.integration.spec.ts) I am configuring the module, defining the Client entity type and including the AppDataServiceModule which in turn does this:
import { NgModule } from '#angular/core';
import { ClientDataService } from './client/client-data.service';
import { EntityDataService, EntityDefinitionService } from '#ngrx/data';
#NgModule({
providers: [
ClientDataService,
],
})
export class AppDataServiceModule {
constructor(
entityDataService: EntityDataService,
clientDataService: ClientDataService
) {
entityDataService.registerService('Client', clientDataService);
}
}
As you can see I am registering the data service as suggested by the ngrx docs, here
I feel like I am pretty close but just need a nudge in the right direction to get it working!
A custom DataService has to extend the DefaultDataService. Should look something like this:
export class ClientDataService extends DefaultDataService<Client> {
constructor(
http: HttpClient, httpUrlGenerator: HttpUrlGenerator
) {
super('Client', http, httpUrlGenerator);
}
public getAll(): Observable<any> {
// get Data here
}
}
The BackendService has to return the Observable:
public getClients(): Observable<Array<Client>> {
// will be mocked
return of([
{
id: '1: Will not return as it will be mocked'
},
{
id: '2: Will not return as it will be mocked'
}
])
}
There are two more things which look suspicious to me:
There is no subscription in the code, so I assume your Observable is cold.
The clientResolve.resolve({}, {}) call expects an ActivatedRouteSnapshot as first parameter. I'm not so familiar with the Resolve interface but maybe thats an issue too.
Related
First of all the question title doesn't make sense. Im not sure how can I ask the question in a better way.
Im facing an issue in an Angular 8.1 project. There is a josn file Im importing that in the settings Class (its a service).
When the environment.ts file have a variable as app_company. the same name a json file also available. So if the client app_company matches the json file. all the config should be loaded from that file. This is what I need to achieve
What I tried so far is .
import { Injectable } from '#angular/core';
import { environment } from '#env/environment';
import * as client1 from './client1.json';
#Injectable({
providedIn: 'root'
})
export class SettingsService {
newInstance:object;
constructor() { }
config(key : string){
// const newInstance = Object.create(window[environment.app_company].prototype);
// newInstance.constructor.apply(newInstance);
// return newInstance.key;
// const newInstance: any = new (<any>window)[environment.app_company];
// console.log(Object.create(window[environment.app_company]));
return environment.app_company.key;
}
}
my json file will look like below
{
"opNumberLabel" : "OP No"
}
So in the return section if I call client1.opNumberLabel
It works like my expectation , but I try to make that dynamic like environment.app_company.key it not working .
As you can see my tried are commented those are not worked so far :(.
Any hint will highly appreciated .
Thanks in advance,
Finally I resolved.
export class SettingsService {
clients = { client1 , client2 };
app_company :string;
default_client = 'client1';
constructor() { }
config(label : string){
this.app_company = environment.app_company;
if(this.clients[this.app_company].default.hasOwnProperty(label)){
return this.clients[this.app_company].default[label];
}else{
return this.clients[this.default_client].default[label];
}
}
}
Thanks to #Titus for the hint.
Would you like to achieve something like that?
import { Injectable } from '#angular/core';
import * as settings from "./environment.json";
interface Settings {
[key: string]: string
}
type Environment = {
[key: string]: Partial<Settings>
};
#Injectable({
providedIn: 'root'
})
export class SettingsService {
private environment: Environment = {
app_company: { }
};
constructor() {
this.environment.app_company = settings;
console.log(this.config("opNumberLabel")); // OP No
}
config(key : string){
return this.environment.app_company[key];
}
}
{
"opNumberLabel" : "OP No",
"settings1": "testSettings1",
"settings2": "testSettings2"
}
If you would like to have app_company as a dynamic value too, then I would suggest to extend the example:
by creating an init function which could also accept the name of
the client.
by passing an object to the SettingsService
constructor, with InjectionToken, so you can define what is the
current client you are trying to configure.
You might also want to have different environment setup for different clients, so I would suggest to create separate environment json files for each client.
You could create an object which would hold every <client>.environment.json with <client> key.
After that you have an object, like that:
const clientEnvironments: Environment = {
client1: { ... },
client2: { ... }
};
So by having the name of your current client, you can easily select which environment you want to use.
In my Angular Route, I define that I'm using Hash strategy:
// app-routing-module.ts
#NgModule({
imports: [
RouterModule.forRoot(routes, {
useHash: true
})
],
//...
When I want to test the current Router url, I run this code:
import { Router } from '#angular/router';
// ..
constructor(private _router: Router) {}
getUrl() {
return this._router.url; // always return '/'
}
The this._router.url is always equal to '/'. However, if I test window.location.href, I'm getting a different value (the real full url).
How do I get the current router-url (in the Angular way, not via window object) while using Hash Strategy?
You can use PlatformLocation class like this:
import { PlatformLocation } from '#angular/common';
constructor(private pLocation: PlatformLocation) { }
getUrl() {
return (pLocation as any).location.href;
}
The reason I coded (pLocation as any) is that location is not showing it typescript intellisense, as you can see it is not showing in Angular docs, but it is there and you can use it.
SIMPLE DEMO
You should use ActivatedRoute to get URL as Numichi mentioned
I'm using NestJS as the framework for a client API. Within the framework we are using a pretty standard Passport/JWT auth infrastructure that is working fine. Our AuthGuard is firing when the bearer token is found and, in secure API endpoints, I can inject the HTTP context via '#Res() request' and get access to the 'request.user' property which contains the payload of my Jwt token.
On top of this we are attempting to implement a 'RolesGuard' in a very similar fashion to the sample code provided in the documentation and some of the sample projects on GitHub (none of which actually use this guard but they include it as a sample guard).
Our issue is that our AuthGuard fires and validates the Jwt token and THEN our RolesGuard fires but the request object it is passed does not have the user meta-data attached to the request.
The key code in our RolesGuard is:
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user) {
return false;
}
In the above snipped the user is always false. Has anyone written a role/permission based guard in Nest that successfully gets access to the scope of the current user? All the code is firing and everything appears registered correctly.
-Kevin
Ultimately this appears to be an ordering issue with the guards and it doesn't look like it can be easily resolved (without the framework allowing some control over the ordering).
My hope was to register the RolesGuard globally but that causes it to be registered first and fire first.
#UseGuards(AuthGuard('jwt'), RolesGuard)
#Roles('admin')
If I register it at the endpoint level and put it after the AuthGuard then it fires second and I get the user context I am expecting within the guard itself. It isn't perfect but it works.
-Kevin
register RoleGuard at the endpoint level and put it after the AuthGuard then it fires second and I get the user context I am expecting within the guard itself.
don't register RoleGuard at module causes it'll be registered first and fire first.
*.module.ts
imports: [],
providers: [{provide: APP_GUARD, useClass: RolesGuard} ,], // remove guard
controllers: [],
exports: [],
Make your RolesGuard extend AuthGuard('StrategyName') and then call super.canActivate for example:
#Injectable()
export class RolesGuard extends AuthGuard('jwt') {
async canActivate(context: ExecutionContext): Promise<boolean> {
// call AuthGuard in order to ensure user is injected in request
const baseGuardResult = await super.canActivate(context);
if(!baseGuardResult){
// unsuccessful authentication return false
return false;
}
// successfull authentication, user is injected
const {user} = context.switchToHttp().getRequest();
}
}
In other words you have to Authenticate first then Authorize
If anyone else stumbles across this question: putting multiple guards into one #UseGuards decorator works, but if you want to keep them separated (say, if you use a custom decorator), you can give the 2nd guard access to req.user by placing it before the #UseGuards call that puts the user on the request object, as in this example:
#RestrictTo(UserAuthorities.admin)
#UseGuards(JwtAuthGuard)
#Get("/your-route")
It seems that this is a consequence of how decorators work in TypeScript.
You can also use multiple roles for role-based Authentication.
In UserResolver
import { Args, Mutation, Query, Resolver } from '#nestjs/graphql';
import { UseGuards } from '#nestjs/common';
import { RolesGuard } from 'src/guards/auth.guard';
#UseGuards(new RolesGuard(['admin']))
#Resolver()
export class UserResolver { ... }
In RolesGuard
import { ExecutionContext, Injectable, UnauthorizedException } from '#nestjs/common';
import { ExecutionContextHost } from '#nestjs/core/helpers/execution-context-host';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class RolesGuard extends AuthGuard('jwt') {
constructor(private roles: string[] | null) {
super();
}
canActivate(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
return super.canActivate(new ExecutionContextHost([req]));
}
handleRequest(err: any, user: any, info: string) {
if (!this.roles) {
return user || null;
}
if (!user) {
throw new UnauthorizedException('Not Valid User.');
}
const role = user.role;
const doesRoleMatch = this.roles.some(r => r === role);
if (!doesRoleMatch) {
throw new UnauthorizedException('Not Valid User.');
}
return user;
}
}
I want to use service in global interceptor.
my code look like this :
import { VariablesService } from '../app/modules/variables/variables.service';
#Interceptor()
export class globalInterceptor implements NestInterceptor {
constructor(private service: VariablesService) {
console.log('contructor running', service); //getting null here
}
and on server.ts first i was initializing like this :
app.useGlobalInterceptors(new globalInterceptor())
but after the injection of service i have to do some modification because parameters are needed now in globalInterceptor()
const variableService = await app.get<VariablesService>(VariablesService);
app.useGlobalInterceptors(new globalInterceptor(variableService));
Now what the problem is I am getting service is null and I am unable to create the object of the service.
GitHub issue link
You can register a global interceptor directly from the module definition:
import { Module } from '#nestjs/common';
import { APP_INTERCEPTOR } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: GlobalInterceptor,
},
],
})
export class ApplicationModule {}
This is listed in the official documentation, here.
After that you must import VariablesService module in your current interceptor module for dipendancy injection
constructor(#Inject(VariablesService) private service: VariablesService) {
console.log('contructor running', service); //
}
I have a simple code which invokes a real HTTP request :
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{person?.id}}</h2>
</div>
`,
})
export class App {
name:string;
constructor(public http: Http) {
this.http.get('https://jsonplaceholder.typicode.com/posts/1')
.map(res => res.json()).subscribe(res => {
this.person = res;
},()=>{},()=>console.log('complete'));
}
}
But now I want to mock the request so that it will fetch data from a file containing :
export arrFakeData:any = {id:1};
I don't want to use a service . I want to mock the request.
Some examples shows to use XHRBackend and some shows how to extend the HTTP class, but they doesn't say how can I force the data to retrieve
I know that I should use
providers:[ /*{ provide: XHRBackend, useClass: MockBackend }*/]
But I don't know how.
Question:
How can I mock http request and return (for GET) the array from arrFakeData ?
PLUNKER
Personally, I would replace the this.http.get method call with an Observable.of so that you can continue programming against the same interface (Observable) without impacting the development of your components.
However, if you really want to do this then you will have to create a service that attaches a listener to all the incoming requests and returns an appropriate mock response using the tools provided by the #angular/http/testing module.
The service will look something as such:
import {Injectable} from "#angular/core";
import {MockBackend, MockConnection} from "#angular/http/testing";
import {arrFakeData} from "./fakeData";
import {ResponseOptions, Response} from "#angular/http";
#Injectable()
export class MockBackendService {
constructor(
private backend: MockBackend
) {}
start(): void {
this.backend.connections.subscribe((c: MockConnection) => {
const URL = "https://jsonplaceholder.typicode.com/posts/1";
if (c.request.url === URL) { // You can also check the method
c.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(arrFakeData)
})));
}
});
}
}
Once you have done this, you need to register all the services and make sure that the Http module is using the MockBackend instead of the XHRBackend.
#NgModule({
imports: [BrowserModule, HttpModule,],
declarations: [App],
providers: [
MockBackend,
MockBackendService,
BaseRequestOptions,
{
provide: Http,
deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: MockBackend, options: BaseRequestOptions) => {
return new Http(backend, options);
}
}
],
bootstrap: [App]
})
export class AppModule {
}
Last but not least, you have to actually invoke the start method, which will make sure that you will actually receive the mock data from the MockBackend. In your AppComponent you can do the following.
constructor(public http: Http, public mockBackendService: MockBackendService) {
this.mockBackendService.start();
this.http.get('https://jsonplaceholder.typicode.com/posts/1')
.map(res => res.json())
.subscribe(res => {
this.person = res;
});
}
I hope this helps! See the plunker for the full example. https://plnkr.co/edit/h111to5PxbI97FIyKGJZ?p=preview
You can just make the http endpoint a JSON file containing whatever data you need. This is exactly how we did it on my last project for Google, and how I do it in my own side projects. We didn't bother mocking up http services and so on, we just pointed at a json file and left everything else the same.