Avoid replacing/mixing user contexts in apollo-server datasources during the execution of asynchronous code - apollo

This question might be somewhat stupid but I like to make sure to not mess this up:
If you use this.context in your datasource, it's critical to create
a new instance in the dataSources function and to not share a single
instance. Otherwise, initialize may be called during the execution of
asynchronous code for a specific user, and replace the this.context
by the context of another user.
Source
Question: Is the following appropriate to avoid mixing/replacing user context during execution of async code?
main.js
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => {
return {
foo: new FooAPI() <- is this considered 'create a new instance in the dataSources function'?
}
},
context: (event) => {
return {
use_id: event.user_id <- (provided by auth middleware)
}
}
})
MyCustomDatasource.js
class MyCustomDatasource extends DataSource {
constructor ({ type, tableName, region, apiVersion }) {
super()
// db specific stuff
}
initialize (config) {
this.context = config.context <- save due to 'new instance in the dataSources function'?
this.cache = config.cache
.
.
.
}
FooAPI.js
class FooAPI extends MyCustomDatasource {
constructor () {
super({
// foo db params
})
}
// Query
async allByUser () {
return await super.query({
KCE: `
#user_id = :user_id
AND begins_with(#id, :id)
`,
EAN: {
'#user_id': 'user_id',
'#id': 'id'
},
EAV: {
':user_id': this.context.user_id, <- save due to 'new instance in the dataSources function'?
':id': 'foo'
},
Limit: 10
})
}
foo resolver
{
Query: {
foos (parent, args, context, info) {
const { foo } = context.dataSources
return foo.allByUser()
}
},

Related

Nested resolvers with depth greater than 1

The Problem
Looking at this GraphQL query,
query {
asset {
name
interfaces {
created
ip_addresses {
value
network {
name
}
}
}
}
}
How do I define a resolver for just the network field on ip_addresses?
My First Thought
Reading docs the give examples of single nested queries, e.g
const resolverMap = {
Query: {
author(obj, args, context, info) {
return find(authors, { id: args.id });
},
},
Author: {
posts(author) {
return filter(posts, { authorId: author.id });
},
},
};
So I thought - why not just apply this pattern to nested properties?
const resolverMap = {
Query: {
asset,
},
Asset: {
interfaces: {
ip_addresses: {
network: () => console.log('network resolver called'),
},
},
},
};
But this does not work, when I run the query - I do not see the console log.
Further Testing
I wanted to make sure that a resolver will always be called if its on root level of the query return type.
My hypothesis:
Asset: {
properties: () => console.log('properties - will be called'), // This will get called
interfaces: {
created: () => console.log('created - wont be called'),
ip_addresses: {
network_id: () => console.log('network - wont be called'),
},
},
},
And sure enough my console showed
properties - will be called
The confusing part
But somehow apollo is still using default resolvers for created and ip_addresses, as I can see the returned data in playground.
Workaround
I can implement "monolith" resolvers as follows:
Asset: {
interfaces,
},
Where the interfaces resolver does something like this:
export const interfaces = ({ interfaces }) =>
interfaces.map(interfaceObj => ({ ...interfaceObj, ip_addresses: ip_addresses(interfaceObj) }));
export const ip_addresses = ({ ip_addresses }) =>
ip_addresses.map(ipAddressObj => ({
...ipAddressObj,
network: network(null, { id: ipAddressObj.network_id }),
}));
But I feel that this should be handled by default resolvers, as these custom resolvers aren't actually doing anything, but passing data down to another resolver.
The resolver map passed to the ApolloServer constructor is an object where each property is the name of a type in your schema. The value of this property is another object, wherein each property is a field for that type. Each of those properties then maps to a resolver function for that specified field.
You posted a query without posting your actual schema, so we don't know what any of your types are actually named, but assuming the network field is, for example, Network, your resolver map would need to look something like:
const resolver = {
// ... other types like Query, IPAddress, etc. as needed
Network: {
name: () => 'My network name'
}
}
You can, of course, introduce a resolver for any field in the schema. If the field returns an object type, you return a JavaScript Object and can let the default resolver logic handle resolving "deeper" fields:
const resolvers = {
IPAddress: {
network: () => {
return {
name: 'My network name',
}
}
}
}
Or...
const resolvers = {
Interface: {
ip_addresses: () => {
return [
{
value: 'Some value',
network: {
name: 'My network name',
},
},
]
}
}
}
Where you override the default resolver just depends at what point the data returned from your root-level field no longer matches your schema. For a more detailed explanation of the default resolver behavior, see this answer.

How can i work with GraphQL Mutation?

how can i work with resolvers for mutations after i create type Mutations in graphql-yoga?
i've tried to create resolvers for mutations, but when i run in graph playground, i the code return error.
and here's my code:
const { GraphQLServer } = require('graphql-yoga')
// 1
const typeDefs = `
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!): User!
}
type User {
id: ID!
name: String!
}
`
// 2
const resolvers = {
Query: {
users: () => User,
},
Mutation: {
// WHAT SHOULD I WRITE IN HERE?
}
}
// 3
const server = new GraphQLServer({
typeDefs,
resolvers,
})
server.start(() => console.log(`Server is running on http://localhost:4000`))
if someone know how can i do for resolvers mutation, can shared with me?
thanks
Resolver for createUser can be defined as follows:
const resolvers = {
Query: {
// Query resolvers
},
Mutation: {
createUser: (parent, args) => {
// Business logic. Maybe save record in database
// Return created user. I am returning dummy data for now, so that you can test it in playground
return {id: 1, name: "John}
}
}
}
Finally it works for me.
i used this:
const resolvers = {
Query: {
users: () => User
},
Mutation: {
createUser: (source, {input}) => {
let newUser = [];
newUser.id = id;
newUser.name = input.name;
User.push(newUser);
return newUser;
}
}
}

Angular 2/Jasmine, updating an activated route params subscription within each describe/it block

Given a simple component that subscribes to the activated route query params in ngOnInit:
export class FooComponent implements OnInit {
private queryParams: any;
constructor(
private activatedRoute: ActivatedRoute
) { }
ngOnInit() {
this.activatedRoute.queryParams.subscribe(params => this.queryParams = params);
}
active(foo: number): boolean {
return this.queryParams['foo'] &&
foo === +this.queryParams['foo'];
}
}
The active function should return true when the foo query param is present and its value matches the supplied parameter.
In the accompanying unit tests for this component, I want to change the value of the query params within each it block to test the query param not being present, matching the parameter and not matching the parameter.
describe('FooComponent', () => {
let component: FooComponent;
let fixture: ComponentFixture<FooComponent>;
let activatedRoute: ActivatedRoute;
class MockActivatedRoute {
queryParams = Observable.of({});
}
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [FooComponent],
providers: [
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FooComponent);
component = fixture.componentInstance;
fixture.detectChanges();
activatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
});
describe('active', () => {
it('should return false if the foo query param is not present', () => {
activatedRoute.queryParams = Observable.of({});
let result = component.active(100);
expect(result).toBe(false);
});
it('should return false if the foo query param does not match the supplied parameter', () => {
activatedRoute.queryParams = Observable.of({ foo: '500' });
let result = component.active(100);
expect(result).toBe(false);
});
it('should return true if the foo query param does not match the supplied parameter', () => {
activatedRoute.queryParams = Observable.of({ foo: '500' });
let result = component.active(500);
expect(result).toBe(true);
});
});
});
Rather the value of the private queryParams member of the FooComponent class does not update within each it block. I've tried the various methods of async, fixture.whenStable(), and fakeAsync/tick.
How do I update the value of the subscription for each unit test?
It's because you are assigning a new Observable, but the client is already subscribed to the first Observable. This happens because ngOnInit is called when you first call fixture.detectChanges(). If you waited to called fixture.detectChanges() after you assign the new Observable to the queryParams, then that Observable would be used.
Another option (maybe preferred) is to instead of using an Observable, you can use a Subject. With this, you can control when data is emitted, and what to emit.
import { Subject } from 'rxjs/Subject'
import { fakeAsync, tick } from
class MockActivatedRoute {
queryParams = new Subject<any>();
}
let route: MockActivatedRoute;
beforeEach(() => {
/* configure */
route = <MockActivatedRoute>TestBed.get(ActivatedRoute);
})
it('', fakeAsync(() => {
route.queryParams.next(newparams); // emit something
tick(); // wait for resolution
fixture.detectChanges(); // detect changes (for ui)
expect(...)
}))
I say this options might be preferred as it allows for emitting multiple values in the same test.

Test async middleware in redux with thunk

I have a middleware that waits for a ARTICLE_REQUEST action, performs a fetch and dispatches either an ARTICLE_SUCCESS or an ARTICLE_FAILURE action when fetch is done. Like so
import { articleApiUrl, articleApiKey } from '../../environment.json';
import { ARTICLE_REQUEST, ARTICLE_SUCCESS, ARTICLE_FAILURE } from '../actions/article';
export default store => next => action => {
// Prepare variables for fetch()
const articleApiListUrl = `${articleApiUrl}list`;
const headers = new Headers({ 'Content-Type': 'application/json', 'x-api-key': articleApiKey });
const body = JSON.stringify({ ids: [action.articleId] });
const method = 'POST';
// Quit when action is not to be handled by this middleware
if (action.type !== ARTICLE_REQUEST) {
return next(action)
}
// Pass on current action
next(action);
// Call fetch, dispatch followup actions and return Promise
return fetch(articleApiListUrl, { headers, method, body })
.then(response => response.json());
.then(response => {
if (response.error) {
next({ type: ARTICLE_FAILURE, error: response.error });
} else {
next({ type: ARTICLE_SUCCESS, article: response.articles[0] });
}
});
}
I really wonder how to test this async code. I want to see if the follow-up actions will be dispatched properly and maybe if the fetch call gets invoked with the proper URL and params. Can anyone help me out?
PS: I am using thunk although I am not absolutely sure of its function as I just followed another code example
You can mock the fetch() function like so:
window.fetch = function () {
return Promise.resolve({
json: function () {
return Prommise.resolve({ … your mock data object here … })
}
})
}
Or you wrap the entire middleware in a Function like so:
function middlewareCreator (fetch) {
return store => next => action => { … }
}
and then create the middleware with the actual fetch method as parameter, so you can exchange it for tests or production.

Mocking RouterStateSnapshot in Jasmine testing

Although I have been writing Angular 2 for a while now, I am only just writing my first Jasmine tests and have run into a little difficulty. I am trying to test that the CanActivate method of service implementing CanActivate is behaving itself, and is returning true or false as expected.
My method looks like this:
canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> {
return this.store$
.map( ( store: StoreState ) => store.currentUser )
.first()
.map( ( user ) => {
if ( user.isAuthenticated ) {
return true;
}
// TODO: This needs refactoring. Need to provide RouterStateSnapshot in test,
// rather than ignoring it!
this.redirectUrl = state ? state.url : '';
this.injector.get( Router ).navigate( ['/login'] );
return false;
} );
}
An extract of my test looks like this:
service = TestBed.get( AuthGuardService );
it( 'should prevent navigation', () => {
service.canActivate(null, null).subscribe((res) => expect( res ).toBeTruthy() );
} );
How do I mock/stub/whatever the second parameter of my call to service.canActivate, rather than simply passing in null?
describe('AuthGuard', () => {
let mockSnapshot: RouterStateSnapshot;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
// so we can get the Router injected
RouterTestingModule,
// other imports as needed
],
// usual config here
});
// create a jasmine spy object, of the required type
// toString is because we have to mock at least one method
mockSnapshot = createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
});
it('should prevent non-authenticated access',
async(inject([AuthGuard, AuthService, Router], (guard: AuthGuard, auth: AuthService, router: Router) => {
// ensure we're logged out
auth.logout();
// set the url on our mock snapshot
mockSnapshot.url = '/protected';
// so we can spy on what's been called on the router object navigate method
spyOn(router, 'navigate');
expect(guard.canActivate(null, mockSnapshot)).toBeFalsy();
// check that our guard re-directed the user to another url
expect(router.navigate).toHaveBeenCalled();
})));
});
})
Here is my solution which I used for unit testing of Custom Router State Serializer
custom-serializer.ts
import { RouterStateSerializer } from '#ngrx/router-store';
import { RouterStateSnapshot, Params } from '#angular/router';
/**
* The RouterStateSerializer takes the current RouterStateSnapshot
* and returns any pertinent information needed. The snapshot contains
* all information about the state of the router at the given point in time.
* The entire snapshot is complex and not always needed. In this case, you only
* need the URL and query parameters from the snapshot in the store. Other items could be
* returned such as route parameters and static route data.
*/
export interface RouterStateUrl {
url: string;
params: Params;
queryParams: Params;
}
export class CustomRouterStateSerializer
implements RouterStateSerializer<RouterStateUrl> {
serialize(routerState: RouterStateSnapshot): RouterStateUrl {
let route = routerState.root;
while (route.firstChild) {
route = route.firstChild;
}
const { url, root: { queryParams } } = routerState;
const { params } = route;
// Only return an object including the URL, params and query params
// instead of the entire snapshot
return { url, params, queryParams };
}
}
custom-serializer.spec.ts
import { CustomRouterStateSerializer } from './utils';
import { RouterStateSnapshot } from '#angular/router';
describe('Utils CustomRouterStateSerializer', () => {
let mockSnapshot: RouterStateSnapshot;
let serializer: CustomRouterStateSerializer;
let mockSnapshotProxy;
beforeEach(() => {
mockSnapshot = jasmine.createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
serializer = new CustomRouterStateSerializer();
});
it('should serialize RouterStateSnapshot to subset of params', () => {
mockSnapshotProxy = new Proxy(mockSnapshot, {
get(target, prop) {
if (prop === 'root') {
return {
params: {
id: 100
},
queryParams: {
name: 'John'
}
};
} else if (prop === 'url') {
return '/orders';
}
},
});
const result = serializer.serialize(mockSnapshotProxy);
expect(result.url).toBe('/orders');
expect(result.params.id).toBe(100);
expect(result.queryParams.name).toBe('John');
});
});
I used jasmine.createSpyObj to create object with proper type and Proxy to pass in required properties