Skip to content

Commit ef7d8c6

Browse files
Merge pull request #3953 from mateuswgoettems/feat/federation-subscription-tests
test(@nestjs/apollo): add e2e coverage for subscriptions with ApolloFederationDriver
2 parents e1e841c + 2cd85b4 commit ef7d8c6

5 files changed

Lines changed: 200 additions & 0 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { DynamicModule, Module } from '@nestjs/common';
2+
import { GraphQLModule } from '@nestjs/graphql';
3+
import { ApolloDriverConfig } from '../../../lib';
4+
import { ApolloFederationDriver } from '../../../lib/drivers';
5+
import { NotificationModule } from './notification.module';
6+
7+
export type FederationAppModuleConfig = {
8+
subscriptions?: ApolloDriverConfig['subscriptions'];
9+
};
10+
11+
@Module({})
12+
export class FederationAppModule {
13+
static forRoot(options?: FederationAppModuleConfig): DynamicModule {
14+
return {
15+
module: FederationAppModule,
16+
imports: [
17+
NotificationModule,
18+
GraphQLModule.forRoot<ApolloDriverConfig>({
19+
driver: ApolloFederationDriver,
20+
autoSchemaFile: true,
21+
includeStacktraceInErrorResponses: false,
22+
subscriptions: options?.subscriptions,
23+
}),
24+
],
25+
};
26+
}
27+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Module } from '@nestjs/common';
2+
import { NotificationResolver } from './notification.resolver';
3+
4+
@Module({
5+
providers: [NotificationResolver],
6+
})
7+
export class NotificationModule {}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Args, Query, Resolver, Subscription } from '@nestjs/graphql';
2+
import { PubSub } from 'graphql-subscriptions';
3+
import { Notification } from './notification';
4+
5+
export const pubSub = new PubSub();
6+
7+
@Resolver(() => Notification)
8+
export class NotificationResolver {
9+
@Query(() => Notification)
10+
getFederatedNotification(): Notification {
11+
return { id: '0', recipient: 'system', message: 'ok' };
12+
}
13+
14+
@Subscription(() => Notification, {
15+
filter(payload, variables) {
16+
return (
17+
payload.newFederatedNotification.id === variables.id &&
18+
payload.newFederatedNotification.recipient === variables.recipient
19+
);
20+
},
21+
})
22+
newFederatedNotification(
23+
@Args('id', { nullable: false }) id: string,
24+
@Args('recipient', { nullable: false }) recipient: string,
25+
) {
26+
return pubSub.asyncIterableIterator('newFederatedNotification');
27+
}
28+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Field, ObjectType } from '@nestjs/graphql';
2+
3+
@ObjectType()
4+
export class Notification {
5+
@Field({ nullable: false })
6+
id: string;
7+
8+
@Field({ nullable: false })
9+
recipient: string;
10+
11+
@Field({ nullable: false })
12+
message: string;
13+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { INestApplication } from '@nestjs/common';
2+
import { Test } from '@nestjs/testing';
3+
import { InMemoryCache } from 'apollo-cache-inmemory';
4+
import ApolloClient from 'apollo-client';
5+
import { gql } from 'graphql-tag';
6+
import { Client, createClient } from 'graphql-ws';
7+
import request from 'supertest';
8+
import ws from 'ws';
9+
import { FederationAppModule } from './federation-app/federation-app.module';
10+
import { pubSub } from './federation-app/notification.resolver';
11+
import { GraphQLWsLink } from './utils/graphql-ws.link';
12+
13+
const subscriptionQuery = gql`
14+
subscription FederatedSubscription($id: String!, $recipient: String!) {
15+
newFederatedNotification(id: $id, recipient: $recipient) {
16+
id
17+
recipient
18+
message
19+
}
20+
}
21+
`;
22+
23+
describe('GraphQL Federation - graphql-ws subscriptions', () => {
24+
let app: INestApplication;
25+
let wsClient: Client;
26+
let port: number;
27+
28+
beforeEach(async () => {
29+
const module = await Test.createTestingModule({
30+
imports: [
31+
FederationAppModule.forRoot({
32+
subscriptions: { 'graphql-ws': {} },
33+
}),
34+
],
35+
}).compile();
36+
37+
app = module.createNestApplication();
38+
await app.init();
39+
await app.listen(0);
40+
port = app.getHttpServer().address().port;
41+
});
42+
43+
afterEach(async () => {
44+
try {
45+
await wsClient?.dispose();
46+
} catch {}
47+
await app.close();
48+
});
49+
50+
it('should connect and receive a filtered notification', async () => {
51+
wsClient = createClient({
52+
url: `ws://localhost:${port}/graphql`,
53+
webSocketImpl: ws,
54+
retryAttempts: 0,
55+
});
56+
57+
wsClient.on('connected', () => {
58+
setTimeout(() => {
59+
pubSub.publish('newFederatedNotification', {
60+
newFederatedNotification: {
61+
id: '99',
62+
recipient: 'alice',
63+
message: 'wrong id',
64+
},
65+
});
66+
pubSub.publish('newFederatedNotification', {
67+
newFederatedNotification: {
68+
id: '1',
69+
recipient: 'bob',
70+
message: 'wrong recipient',
71+
},
72+
});
73+
pubSub.publish('newFederatedNotification', {
74+
newFederatedNotification: {
75+
id: '1',
76+
recipient: 'alice',
77+
message: 'Hello from federation',
78+
},
79+
});
80+
}, 100);
81+
});
82+
83+
const apolloClient = new ApolloClient({
84+
link: new GraphQLWsLink(wsClient),
85+
cache: new InMemoryCache(),
86+
});
87+
88+
await new Promise<void>((resolve, reject) => {
89+
apolloClient
90+
.subscribe({
91+
query: subscriptionQuery,
92+
variables: { id: '1', recipient: 'alice' },
93+
})
94+
.subscribe({
95+
next(value: any) {
96+
try {
97+
expect(value.data.newFederatedNotification.id).toEqual('1');
98+
expect(value.data.newFederatedNotification.recipient).toEqual(
99+
'alice',
100+
);
101+
expect(value.data.newFederatedNotification.message).toEqual(
102+
'Hello from federation',
103+
);
104+
resolve();
105+
} catch (e) {
106+
reject(e);
107+
}
108+
},
109+
complete() {},
110+
error: reject,
111+
});
112+
});
113+
});
114+
115+
it('should expose the Subscription type in the federation SDL', async () => {
116+
const response = await request(app.getHttpServer())
117+
.post('/graphql')
118+
.send({ query: '{ _service { sdl } }' })
119+
.expect(200);
120+
121+
const sdl: string = response.body.data._service.sdl;
122+
expect(sdl).toContain('type Subscription');
123+
expect(sdl).toContain('newFederatedNotification');
124+
});
125+
});

0 commit comments

Comments
 (0)