can't run apollo-server-express with merged typeDefs and resolvers using #graphql-tools/merge - apollo

hey friends this is the structure of my project and files down below:
starting from app.js file:
import { Server } from "./src/Server";
import { config } from "dotenv";
config();
Server.StartServer();
Down below is the Server.ts file that is bootstrapping of the apollo-server-express
import express from "express";
import http from "http";
import { ApolloServer } from "apollo-server-express";
import { GraphQLServerOptions } from "apollo-server-core/src/graphqlOptions";
import { schema } from "./graphql/index";
export class Server {
public static StartServer() {
const app: express.Application = express();
const server = new ApolloServer({
schema,
graphiql: true,
} as unknown as GraphQLServerOptions);
server.applyMiddleware({ app });
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen(process.env.PORT, function () {
console.log(`server is running on port ${process.env.PORT}`);
});
}
}
this is user.resolvers.ts file that my resolver goes here:
import { IResolvers } from "graphql-tools";
export const resolver: IResolvers = {
Mutation: {
register: function (parent, args, content, info) {
console.log(parent);
},
},
Query: {
getUser: function (parent, args, content, info) {
console.log(parent);
},
},
};
And here we go with the typeDef for in user.schema.ts file:
import { gql } from "apollo-server-express";
export const typeDef = gql`
type User {
username: String
password: String
}
type Mutation {
register(input: {username: String!, passwprd: String!})
}
type Query {
getUSer(username): User
}
`;
And finally over there in ./src/graphql/index.ts file I'm doing the mergig for resolvers and typeDefs there and I'm making the executableSchema for adding it to ApolloServer config object but I face the error below:
Any Ideas and suggestions would be greatly appreciated. Early thanks for the contributors :)

Related

How can I mock a paginated GraphQL query?

I am using apollo/client and graphql-tools/mock to auto mock graphql queries and test React Native components that use them. My schema is generated from an introspection query created by graphql-codegen. For the most part, my queries are getting mocked by addMocksToSchema just fine. However I have a query that is not returning any mock data.
The query is paginated and doesn't follow the same structure of the examples in the docs (https://www.graphql-tools.com/docs/mocking). Instead of having a query with a node that has a field that is a connection type, the connection is returned from the query. This means I can't use relayStylePaginationMock to mock my function because the resolver argument of addMocksToSchema expects the nodes to be objects not functions(function is the return type of relayStylePaginationMock).
In the below code I have tried overriding the newsPost query with a resolver, but I can't figure out how to get the NewsPostEdges from the store and put them in my mock. Everything I have tried has broken the mock and caused it to return undefined for the whole mocked query.
Why does a paginated mock not work by default?
How can I mock this query?
Schema:
type Query {
newsPost: NewsPostConnection
}
type NewsPostConnection {
totalCount: Int
edges: [NewsPostEdge]!
pageInfo: PageInfo!
}
type NewsPostEdge {
node: NewsPostNode
cursor: String!
}
type NewsPostNode {
newsPostId: Int!
isPinned: Boolean!
label: String
title: String
content: String
postType: NewsPostType!
createdDate: DateTime
createdDateTime: String
creator: UserNode!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
endCursor: String
startCursor: String
}
News Posts query:
query NewsPosts(
$after: String
$first: Int
$newsPostId: Filter_ID
$sort: [NewsPostSortEnum]
$isPinned: Filter_Boolean
) {
newsPosts(
after: $after
first: $first
newsPostId: $newsPostId
sort: $sort
isPinned: $isPinned
) {
pageInfo {
hasNextPage
endCursor
}
edges {
post: node {
newsPostId
postType
isPinned
label
createdDateTime
creator {
initials
avatarUrl
displayName
}
content
}
}
}
}
newsPostsContent.test.tsx
import React from 'react';
import { waitFor } from '#testing-library/react-native';
import { PartialDeep } from 'type-fest';
import { faker } from '#faker-js/faker';
import { createFakeUser, render } from '#root/unit-tests/#util';
import { NewsPostNode, NewsPostType } from '#root/src/generated';
import NewsPostContent from '../NewsPostContent';
const mocks = {
NewsPostNode: (): PartialDeep<NewsPostNode> => {
const postId = faker.random.numeric(4);
const createdDate = faker.date.recent(10);
return {
postId,
isPinned: true,
label: 'test',
content: `<div><p>${faker.random.words(10)}</p></div>`,
postType: NewsPostType.Announcement,
createdDate: createdDate.toISOString(),
createdDateTime: createdDate.toISOString(),
};
},
UserNode: createUserPerson(),
};
describe('Dashboard News', () => {
it('renders dashboard news', async () => {
const { getByTestId, debug } = render(
<NewsPostContent />,
mocks,
);
await waitFor(() => [debug(), expect(getByTestId('newsPostContent:Card')).toBeDefined()]);
});
});
NewsPostsContetnt.tsx
const NewsPostContent = () => {
const [newsPostList, setNewsPostList] = useState<PartialDeep<NewsPostNode>[]>([])
const {
data,
loading,
refetch: refetchPosts,
} = useNewsPostsQuery({
variables: { first: MAX_POSTS, isPinned: true, sort: [PostSortEnum.CreatedDateDesc] },
});
console.log(data); // <-- returns undefined when mock breaks
useEffect(() => {
const newsPostEdges = data?.newsPosts?.edges ?? [];
const newsPostNodes = newsPostEdges.reduce((posts, newsPostNode) => {
if (newsPostNode?.post) {
posts.push(newsPostNode.post);
}
return posts;
}, [] as PartialDeep<NewsPostNode>[]);
setNewsPostList(newsPostNodes);
}, [data]);
return (
{<View>
// Component UI to render posts
</View>}
)
}
AutoMockedProvider.tsx
import React from 'react';
import { ApolloProvider, ApolloClient, InMemoryCache } from '#apollo/client';
import { buildClientSchema } from 'graphql';
import {
addMocksToSchema,
createMockStore,
IMocks,
IMockStore,
relayStylePaginationMock,
} from '#graphql-tools/mock';
import { SchemaLink } from '#apollo/client/link/schema';
import { faker } from '#faker-js/faker';
const introspectionResult = require('../../src/generated/introspection.json');
const defaultMocks = {
Date: () => faker.date.recent().toISOString(),
DateTime: () => faker.date.recent().toISOString(),
};
const resolvers = (store: IMockStore) => ({
Query: {
newsPosts: (root, { isPinned, after, first, postId, sort }) => {
return {
edges: (ref) => {
const connectionsRef = store.get('NewsPostConnection');
const edgesRef = store.get(connectionsRef, 'edges');
return edgesRef; // <-- this breaks the mock
},
pageInfo: {
endCursor: null,
hasNextPage: false,
},
};
},
},
});
const AutoMockedProvider = ({
mocks = {},
children,
}: React.PropsWithChildren<{ mocks?: IMocks }>) => {
const schema = buildClientSchema(introspectionResult);
const store = createMockStore({ mocks: { ...defaultMocks, ...mocks }, schema });
const schemaWithMocks = addMocksToSchema({
schema,
mocks: {
...defaultMocks,
...mocks,
},
resolvers,
preserveResolvers: false,
store,
});
const client = new ApolloClient({
link: new SchemaLink({ schema: schemaWithMocks }),
cache: new InMemoryCache(),
});
return <ApolloProvider client={client}>{children}</ApolloProvider>;
};
export default AutoMockedProvider;

Mock objection model dependecy in NestJs with Jest

I'm trying to make unit test with nestjs and objection. The problem I have is that I can't mock the "User" Model that is injected with the decorator "#InjectModel". I searched a lot to find a solution but I didn't find anything.
users.service.ts
import { HttpException, HttpStatus, Inject, Injectable } from '#nestjs/common';
import { CreateUserDto } from './create-user.dto';
import { User } from 'src/app.models';
import { InjectModel } from 'nestjs-objection';
#Injectable()
export class UsersService {
constructor(
#InjectModel(User) private readonly userModel: typeof User,
) {}
async create(createUserDto: CreateUserDto) {
try {
const users = await this.userModel.query().insert(createUserDto);
return users
} catch (err) {
console.log(err)
throw new HttpException(err, HttpStatus.BAD_REQUEST);
}
}
}
users.service.spec.ts
import { Test, TestingModule } from "#nestjs/testing";
import { UsersService } from "../src/users/users.service";
import { CreateUserDto } from "src/users/create-user.dto";
import { User } from "../src/app.models";
import { getObjectionModelToken } from 'nestjs-objection';
describe('userService', () => {
let userService: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: User,
useValue: {}
},
],
}).compile();
userService = module.get<UsersService>(UsersService);
});
it('Should be defined', () => {
expect(userService).toBeDefined();
});
it('Should add pin to a created user', async () => {
const createUserDTO: CreateUserDto = {
email: 'mockEmail#mock.com',
userName: 'user'
}
const res = await userService.create(createUserDTO)
expect(res).toHaveProperty('pin')
});
I tried to use import { getObjectionModelToken } from 'nestjs-objection'; inside provider like this:
providers: [
UsersService,
{
provide: getObjectionModelToken(User),
useValue: {}
},
],
I got this error
It asks for a "connection" but I don't know what to put on it.
I suppose "getObjectionModelToken" is the function to mock the "InjectModel". When I pass an empty string
I got this error:
● Test suite failed to run
Cannot find module 'src/app.models' from '../src/users/users.service.ts'
Require stack:
C:/nestjs-project/nestjs-knex/src/users/users.service.ts
users.repository.spec.ts
1 | import { HttpException, HttpStatus, Inject, Injectable } from '#nestjs/common';
2 | import { CreateUserDto } from './create-user.dto';
> 3 | import { User } from 'src/app.models';
| ^
4 | import {
5 | InjectModel,
6 | synchronize,
at Resolver._throwModNotFoundError (../node_modules/jest-resolve/build/resolver.js:491:11)
at Object.<anonymous> (../src/users/users.service.ts:3:1)
If I change the path it breaks the correct functionality of the app
That looks like an error from jest not understanding what src/* imports are. Either use relative imports rather than absolute (e.g. use import { User } from '../app.models') or tell jest how to resolve src/* imports via the moduleNameMapper in your jest.config.js or package.json
{
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/path/to/src/$1"
}
}
I think based on the error your /path/to/src should be ../src but I'm not 100% sure, so make sure you set that correctly.

express-session crashes server on setting cookie?

Basically this other post Express-session does not set cookie? where I'm following Ben Awad's Fullstack Tutorial. The cookie gets created but the server crashes and this is the error
node:internal/errors:464
ErrorCaptureStackTrace(err);
^
TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Array
at new NodeError (node:internal/errors:371:5)
at _write (node:internal/streams/writable:312:13)
at Socket.Writable.write (node:internal/streams/writable:334:10)
at RedisSocket.writeCommand (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/socket.js:57:130)
at Commander._RedisClient_tick (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/index.js:415:64)
at Commander._RedisClient_sendCommand (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/index.js:396:82)
at Commander.commandsExecutor (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/index.js:160:154)
at Commander.BaseClass.<computed> [as set] (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/commander.js:8:29)
at RedisStore.set (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/connect-redis/lib/connect-redis.js:65:21)
at Session.save (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/session/session.js:72:25)
at Session.save (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/index.js:406:15)
at ServerResponse.end (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/index.js:335:21)
at ServerResponse.send (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express/lib/response.js:221:10)
at /home/kuratar/github/milestone-4-Kuratar/server/node_modules/apollo-server-express/dist/ApolloServer.js:89:25 {
code: 'ERR_INVALID_ARG_TYPE'
}
I noticed that this specific line of code in user.ts:
req.session.userId = user.id
when it's commented out, the error doesn't occur but the cookie is not set. There isn't a set-cookie option in the response-header.
My files are pretty much the same as this other person in the post I linked.
index.ts
import "reflect-metadata";
import { MikroORM } from "#mikro-orm/core";
import { __prod__ } from "./constants";
import microConfig from "./mikro-orm.config";
import express from "express";
import { ApolloServer } from "apollo-server-express";
import { ApolloServerPluginLandingPageGraphQLPlayground } from "apollo-server-core";
import { buildSchema } from "type-graphql";
import { HelloResolver } from "./resolvers/hello";
import { PostResolver } from "./resolvers/post";
import { UserResolver } from "./resolvers/user";
import * as redis from "redis";
import session from "express-session";
import connectRedis from "connect-redis";
import { MyContext } from "./types";
// start postgresql server on wsl - sudo service postgresql start
// stop - sudo service postgresql stop
// start redis server on wsl - redis-server
// sudo /etc/init.d/redis-server restart
// stop, start
// watch ts changes - npm run watch
// run server - npm run dev
const main = async () => {
const orm = await MikroORM.init(microConfig); // initialize database
await orm.getMigrator().up(); // run migrations before anything else
const app = express();
app.set("trust proxy", 1); // trust first proxy
// this comes before applyMiddleware since use session middleware inside apollo
const RedisStore = connectRedis(session);
const redisClient = redis.createClient(); // TODO: TypeError: Cannot read properties of undefined (reading 'createClient')
redisClient.on("error", (err) => console.log("Redis Client Error", err));
await redisClient.connect();
app.use(
session({
name: "qid",
// touch - make request to redis to reset the user's session
// if user does something, it means they are active and should reset the timer of automatically logging them out
// after 24 hours for example
// disableTouch: true - keep session forever, can change this later to timed sessions
store: new RedisStore({ client: redisClient, disableTouch: true }), // tell express session using redis
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
httpOnly: true,
sameSite: "lax", // csrf
secure: __prod__, // only works in https
},
saveUninitialized: false,
secret: "askljdhfjkalshdjlf", // want to keep this secret separately
resave: true,
rolling: true,
})
);
// app.use(function (req, res, next) {
// res.header(
// "Access-Control-Allow-Origin",
// "https://studio.apollographql.com"
// );
// res.header("Access-Control-Allow-Credentials", "true");
// next();
// });
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [HelloResolver, PostResolver, UserResolver],
validate: false,
}),
// object that is accessible by resolvers, basically pass the database itself
context: ({ req, res }): MyContext => ({ em: orm.em, req, res }),
plugins: [
ApolloServerPluginLandingPageGraphQLPlayground({
settings: { "request.credentials": "include" },
}),
],
});
await apolloServer.start();
const corsOptions = {
origin: new RegExp("/*/"),
credentials: true,
};
apolloServer.applyMiddleware({ app, cors: corsOptions }); // create graphql endpoint on express
app.listen(4000, () => {
console.log("Server started on localhost:4000");
});
};
main().catch((error) => {
console.log("----------MAIN CATCHED ERROR----------");
console.error(error);
console.log("-----------------END------------------");
});
user.ts
import {
Resolver,
Arg,
Mutation,
InputType,
Field,
Ctx,
ObjectType,
} from "type-graphql";
import { User } from "../entities/User";
import { MyContext } from "../types";
import argon2 from "argon2";
// another way to implementing arguments for methods instead of #Arg()
#InputType()
class UsernamePasswordInput {
#Field()
username: string;
#Field()
password: string;
}
#ObjectType()
class FieldError {
#Field()
field: string;
#Field()
message: string;
}
#ObjectType()
class UserResponse {
#Field(() => [FieldError], { nullable: true })
errors?: FieldError[];
#Field(() => User, { nullable: true })
user?: User;
}
#Resolver()
export class UserResolver {
#Mutation(() => UserResponse)
async register(
#Arg("options") options: UsernamePasswordInput,
#Ctx() { em }: MyContext
): Promise<UserResponse> {
if (options.username.length <= 2) {
return {
errors: [
{ field: "username", message: "length must be greater than 2" },
],
};
}
if (options.password.length <= 2) {
return {
errors: [
{ field: "password", message: "length must be greater than 2" },
],
};
}
// argon2 is a password hasher package
const hashedPassword = await argon2.hash(options.password);
const user = em.create(User, {
username: options.username,
password: hashedPassword,
});
try {
await em.persistAndFlush(user);
} catch (error) {
// duplicate username error
if (error.code === "23505") {
// || error.detail.includes("already exists")
return {
errors: [{ field: "username", message: "Username already taken" }],
};
}
}
// return user in an object since response is now a response object - UserResponse
return { user };
}
#Mutation(() => UserResponse)
async login(
#Arg("options") options: UsernamePasswordInput,
#Ctx() { em, req }: MyContext
): Promise<UserResponse> {
// argon2 is a password hasher package
const user = await em.findOne(User, {
username: options.username,
});
// can give same field error message like invalid login
if (!user) {
return {
errors: [{ field: "username", message: "That username doesn't exist" }],
};
}
const valid = await argon2.verify(user.password, options.password);
if (!valid) {
return {
errors: [{ field: "password", message: "Incorrect password" }],
};
}
// mutation {
// login(options: {username: "eric", password: "eric"}) {
// errors {
// field
// message
// }
// user {
// id
// username
// }
// }
// }
console.log(req.session)
console.log(user.id)
req.session.userId = user.id
console.log(req.session)
console.log(req.session.id)
// console.log(req.session.userId)
// return user in an object since response is now a response object - UserResponse
return { user };
}
}
types.ts
import { EntityManager, IDatabaseDriver, Connection } from "#mikro-orm/core";
import { Request, Response } from "express";
import { Session, SessionData } from "express-session";
// this is the type of orm.em from index.ts
// extracted to make code look cleaner in post.ts
export type MyContext = {
em: EntityManager<any> & EntityManager<IDatabaseDriver<Connection>>;
req: Request & {
session: Session & Partial<SessionData> & { userId?: number };
};
res: Response;
};
I've had the same error. In my situation I was able to fix it by changing the redis client to ioredis(I was using redis).
To be more specific on Bernardo, Ben also changes it to ioredis in the github repo. So you need to install ioredis and add these lines
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
and delete/comment out the old redisClient lines of code.

401 "Unauthorized" error in Django and Angular File Upload

I have created a Django and Angular application to upload files. It was working without errors until I integrated a login page. I have not been able to upload files since integration. I get 401 - "Unauthorized" error. What could have possibly gone wrong?
Auth-interceptor:
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest,HttpErrorResponse } from "#angular/common/http";
import { Injectable } from "#angular/core";
import { catchError, Observable, throwError } from "rxjs";
import { LoginService } from "src/services/login.service";
#Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(private authService: LoginService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.authService.isLoggedIn()) {
const token = this.authService.getAuthToken();
console.log("intercept",token)
// If we have a token, we set it to the header
request = request.clone({
setHeaders: {Authorization: `Token ${token}`}
});
}
return next.handle(request)
}
}
fileupload.component.ts:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup } from '#angular/forms';
import { LoginService } from 'src/services/login.service';
import { FileUploader, FileLikeObject } from 'ng2-file-upload';
import { concat, Observable } from 'rxjs';
import { HttpEvent, HttpEventType } from '#angular/common/http';
#Component({
selector: 'app-fileupload',
templateUrl: './fileupload.component.html',
styleUrls: ['./fileupload.component.scss']
})
export class FileuploadComponent {
DJANGO_SERVER = 'http://127.0.0.1:8081'
public uploader: FileUploader = new FileUploader({});
public hasBaseDropZoneOver: boolean = false;
constructor(private formBuilder: FormBuilder, private uploadService: LoginService) { }
fileOverBase(event): void {
this.hasBaseDropZoneOver = event;
}
getFiles(): FileLikeObject[] {
return this.uploader.queue.map((fileItem) => {
return fileItem.file;
});
}
upload() {
let files = this.getFiles();
console.log(files);
let requests= [];
files.forEach((file) => {
let formData = new FormData();
formData.append('file' , file.rawFile, file.name);
requests.push(this.uploadService.upload(formData));
console.log(requests,file)
});
concat(...requests).subscribe(
(res) => {
console.log(res);
},
}
);
}}
console.log(err);
}
);
}}
service:
public upload(formData) {
let token= localStorage.getItem('token');
return this.http.post<any>(`${this.DJANGO_SERVER}/upload/`, formData).pipe(map((res) => {
console.log(res)
})
)
}
Thank you
I resolved the issue. It was because I was usign interceptor and I was using third party API for authentication. So instead of Django token, the third party APIs token was sent in header of POST request.
How I resolved it?
I used Httpbackend to process POST requests to Django DB so that the request is not intercepted and then I added custom header (with Django token to the reuest). I used the code snippet on this website: https://levelup.gitconnected.com/the-correct-way-to-make-api-requests-in-an-angular-application-22a079fe8413

ember octane test-helpers: currentURL() returns addressbar URL()

I noticed currentURL() from #ember/test-helpers returns the actual test window address bar URL instead of the test's currentUrl().
I made sure that my ENV.locationType=none. Can someone spot something really obvious that I'm missing?
Expected behavior: when user visits '/clientname', they should be redirected to '/clientname/login':
config/environment.js:
module.exports = function (environment) {
let ENV = {
modulePrefix: "portal-client3",
environment,
rootURL: "/",
locationType: "auto",
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
},
EXTEND_PROTOTYPES: {
// Prevent Ember Data from overriding Date.parse.
Date: false,
},
},
APP: {},
};
if (environment === "test") {
ENV.locationType = "none";
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = "#ember-testing";
ENV.APP.autoboot = false;
}
return ENV;
};
app/router:
import EmberRouter from "#ember/routing/router";
import config from "./config/environment";
export default class Router extends EmberRouter {
location = config.locationType;
rootURL = config.rootURL;
}
tests/test-helper:
import Application from "../app";
import config from "../config/environment";
import { setApplication } from "#ember/test-helpers";
import { start } from "ember-qunit";
setApplication(Application.create(config.APP));
start();
tests/acceptance/login-test:
import { module, test } from "qunit";
import { visit, currentURL } from "#ember/test-helpers";
import { setupApplicationTest } from "ember-qunit";
import { setupMirage } from "ember-cli-mirage/test-support";
module("Acceptance | login", function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
test("visiting /login", async function (assert) {
await visit("/clientname");
assert.equal(currentURL(), "/clientname/login");
// I see currentUrl()='/tests/login' instead of '/clientname/login'
});
});
Screenshot: