I have a project with Prisma+NestJS. My database is a master+replication PostgresSQL server with pgBouncer
It's executed in local server but Server has closed the connection happened for some customers when published. This error happened for different tables. This is just an example:
Invalid `this.prisma.readClient.customer.findUnique()` invocation in /usr/src/app/dist/src/modules/global/guards/auth-guard.js
54 const customer = await this.prisma.readClient.customer.findUnique(
Server has closed the connection.
Connection Strings look like this:
DATABASE_WRITE_URL="postgresql://user:******#[masterip]:5432/database?schema=public&pgbouncer=true"
DATABASE_READ_URL="postgresql://user:******:#[replicaip]:5432/stage-v2-panel?schema=public&pgbouncer=true"
PrismaService file:
#Injectable()
export class PrismaService implements OnModuleInit {
readClient: PrismaClient;
writeClient: PrismaClient;
constructor() {
this.readClient = new PrismaClient({
datasources: {db: {url: process.env.DATABASE_READ_URL}},
});
this.writeClient = new PrismaClient({
datasources: {db: {url: process.env.DATABASE_WRITE_URL}},
});
}
async onModuleInit() {
await this.readClient.$connect();
await this.writeClient.$connect();
}
async enableShutdownHooks(app: INestApplication) {
this.readClient.$on('beforeExit', async () => {
await app.close();
});
this.writeClient.$on('beforeExit', async () => {
await app.close();
});
}
}
Related
I am working on creating unit tests for a project that uses Typeorm without Nestjs. The file I am creating unit tests for uses queryRunner to start a transaction. My problem is, I am not able to mock the Datasource. I tried multiple ways but the mock is never getting called to replace the actual Datasource that has access to the postgresql database. I saw some solutions to mock it, but they all use Nestjs, which I don't use in my case.
The error I am having in the tests right now is:
Received promise rejected instead of resolved
Rejected to value: [TypeORMError: Driver not Connected]
Any help would be highly appreciated since I'm not an expert in unit tests.
Here's an example of the code that I am having the issue with (replaced some names tho):
datasource.ts
//database config is defined in this file
export const datasource: DataSource = new DataSource(some_config);
dummy.service.ts
export const dummyService = () => {
//datasource is imported from the above file
const queryRunner = datasource.createQueryRunner();
await queryRunner.startTransaction();
try {
const foundObject = await queryRunner.manager.getRepository(MyObject).findOne({
where: { id: someId },
lock: { mode: 'pessimistic_write' },
});
//some more database calls
} catch (error) {
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
}
mock.datasource.ts
import { DataSource } from 'typeorm';
export const dataSourceMockFactory: () => MockType<DataSource> = jest.fn(
() => ({
createQueryRunner: jest.fn().mockImplementation(() => ({
connect: jest.fn(),
startTransaction: jest.fn(),
release: jest.fn(),
rollbackTransaction: jest.fn(),
manager: {
getRepository: jest.fn().mockImplementation(() => ({
create: jest.fn(),
findOne: jest.fn(() => {
return getMyDummyObject();
}),
})),
save: jest.fn(),
},
})),
}),
);
export type MockType<T> = {
// eslint-disable-next-line #typescript-eslint/ban-types
[P in keyof T]?: jest.Mock<{}>;
};
dummy.unit.test.ts
describe('dummy service test', () => {
let dataSourceMock: MockType<Typeorm.DataSource>;
beforeEach(async () => {
// This method did not work
jest.mock('typeorm', () => {
const actual = jest.requireActual('typeorm');
return {
...actual,
DataSource: dataSourceMockFactory(),
};
});
// The below method did not work either
jest.mock('./../../db/datasource', () => ({
datasource: dataSourceMockFactory(),
}));
});
afterEach(() => {
jest.clearAllMocks();
});
it('should test dummy service with transaction', async () => {
// getting an error here
await expect(
dummyFunction(),
).resolves.not.toThrow();
});
});
I have Mongoose In memory test helper that I'm using with a controller test:
Helper
import { MongooseModule, MongooseModuleOptions } from '#nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { connection } from 'mongoose';
let mongod: MongoMemoryServer;
export const rootMongooseTestModule = (options: MongooseModuleOptions = {}) =>
MongooseModule.forRootAsync({
useFactory: async () => {
mongod = await MongoMemoryServer.create();
const mongoUri = await mongod.getUri();
return {
uri: mongoUri,
...options,
};
},
});
export const closeInMongodConnection = async () => {
connection.dropDatabase();
connection.close();
if (mongod) await mongod.stop();
};
export const removeCollections = async () => {
const collections = connection.collections;
for (const key in collections) {
const collection = collections[key];
await collection.deleteMany({});
}
}
UserController Test
import { Test, TestingModule } from '#nestjs/testing';
import { MongooseModule } from '#nestjs/mongoose';
import {
closeInMongodConnection,
removeCollections,
rootMongooseTestModule,
} from '../../test/utils/MongooseTestModule';
import { UserSchema } from './user.schema';
import { UserController } from './user.controller';
import { UserService } from './user.service';
describe('UserController', () => {
let controller: UserController;
const mockUser = {
username: 'kyle',
email: 'kyle#example.com',
password: 'password',
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
imports: [
rootMongooseTestModule(),
MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]),
],
providers: [UserService],
}).compile();
controller = module.get<UserController>(UserController);
});
afterAll(async () => {
await closeInMongodConnection();
});
afterEach(async () => {
await removeCollections();
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
describe('create', () => {
it('should return the saved object', async () => {
const createdUser = await controller.create(mockUser);
expect(createdUser.username).toBe(mockUser.username);
});
});
});
After the test I close the connection and remove all collections, but for some reason I'm still getting:
npm test --detectOpenHandles
tried this too:
npm test --runInBand --detectOpenHandles
A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. Active timers can also cause this, ensure that .unref() was called on them.
The test does finish and doesn't hang:
Test Suites: 15 passed, 15 total
Tests: 16 passed, 16 total
Snapshots: 0 total
Time: 5.369 s
When I add await connection.dropDatabase(); in the closeInMongodConnection function my tests take about 3 more seconds.
Is there anyway to fix this error? Is this just happening because opening and closing mongo connections is slow? I'm not sure what's going on because it seems like I'm doing everything I'm suppose too
Edit:
After un commenting await connection.dropDatabase(); I'm now getting:
thrown: "Exceeded timeout of 5000 ms for a hook.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
I have the following ApolloServer (v4)
import { MongoDataSource } from 'apollo-datasource-mongodb'
export default class LoaderAsset extends MongoDataSource {
async getAsset(assetId) {
return this.findOneById(assetId) // ERROR IS HERE
}
}
async function startApolloServer(app) {
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })]
});
await server.start();
app.use(
'/graphql',
cors(),
bodyParser.json(),
expressMiddleware(server, {
context: async ({ req }) => {
return {
dataSources: {
loaderAsset: new LoaderAsset(modelAsset),
}
}
},
}),
);
const port = config.get('Port') || 8081;
await new Promise(resolve => httpServer.listen({ port }, resolve));
}
when I run graphql and send one assetId, everything is working till I get following error:
this.findOneById is not a function
By the way (this) has collection and model objects but not any methods.
is it because apollo-datasource-mongodb is not compatible with the new version of apollo server v4?
dataSources in v3 were as follows:
dataSources: () => ({
users: new Users(client.db().collection('users'))
// OR
// users: new Users(UserModel)
})
but in the new version dataSources is inside the context
Maybe the issue is because of this change.
Credit to: https://github.com/systemkrash on https://github.com/GraphQLGuide/apollo-datasource-mongodb/issues/114
what i did is i override my datasource class so I can call the initialize method inside in the MongoDatasource class. Now it works for me.
import { MongoDataSource } from 'apollo-datasource-mongodb';
class ReceptionDataSource extends MongoDataSource {
constructor({ collection, cache }) {
super(collection);
super.initialize({ context: this.context, cache });
}
async getReception(receptionId) {
return await this.findOneById(receptionId);
}
}
export default ReceptionDataSource;
then in my context
async function startApolloServer(app) {
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })]
});
await server.start();
app.use(
'/graphql',
cors(),
bodyParser.json(),
expressMiddleware(server, {
context: async ({ req }) => {
const { cache } = server
return {
dataSources: {
loaderAsset: new ReceptionDataSource({collection: ReceptionModel, cache}),
}
}
},
}),
);
const port = config.get('Port') || 8081;
await new Promise(resolve => httpServer.listen({ port }, resolve));
}
I'm trying to setup testing my node with mongo app using Jest. I've set it up and copied their sample verbatim, which works fine, except when I call the db.close(); function. It gives me a TypeError: that db.close is not a function. This is directly out of their example:
import { MongoClient } from 'mongodb';
describe('insert', () => {
let connection;
let db;
beforeAll(async () => {
connection = await MongoClient.connect(global.__MONGO_URI__, {
useNewUrlParser: true,
});
db = await connection.db(global.__MONGO_DB_NAME__);
});
afterAll(async () => {
await connection.close();
await db.close();
});
it('should insert a doc into collection', async () => {
const users = db.collection('users');
const mockUser = {_id: 'some-user-id', name: 'John'};
await users.insertOne(mockUser);
const insertedUser = await users.findOne({_id: 'some-user-id'});
expect(insertedUser).toEqual(mockUser);
});
});
im having an issue trying to run an e2e test for my NestJS application tha uses a mongodb-memory-server to run mongo in memory
my e2e test looks like this
describe('UsersController.e2e', () => {
let app: INestApplication;
let module: TestingModule;
const mongod = new MongoMemoryServer();
beforeAll(async () => {
const port = await mongod.getPort();
const database = await mongod.getDbName();
module = await Test.createTestingModule({
providers: [UserRepository, UserService],
controllers: [UserController],
imports: [TypeOrmModule.forRootAsync({
useFactory: () => {
return {
type: 'mongodb',
host: '127.0.0.1',
port,
database,
entities: [__dirname + '../../../**/*.entity{.ts,.js}'],
} as TypeOrmModuleOptions;
},
}),
TypeOrmModule.forFeature([User])
]
}).compile();
app = module.createNestApplication();
await app.init();
});
afterAll(async () => {
await module.close();
await app.close();
});
describe('GET /users', () => {
it('should return a collection of user resources', async () => {
const { body } = await supertest
.agent(app.getHttpServer())
.get('/users')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200);
expect(body).toEqual(userCollectionMock);
});
});
});
when running the test it throws a 500 error
Cannot read property 'find' of undefined
TypeError: Cannot read property 'find' of undefined
at UserRepository.Object.<anonymous>.MongoRepository.find (src/repository/MongoRepository.ts:77:29)
at UserRepository.index (src/modules/user/repositories/user.repository.ts:12:20)
the repository class looks like
export class UserRepository extends MongoRepository<User> implements IResourceRepository<User> {
index(): Promise<User[]> {
return this.find();
}
}
the extension of MongoRepository provides find() https://github.com/typeorm/typeorm/blob/master/src/repository/MongoRepository.ts#L76
So it perplexes me as to why it is undefined!
I know this is a month old, but I just ran into this issue and resolved it.
I'm using mongoose + typegoose with mongodb-memory-server.
import { MongoMemoryServer } from 'mongodb-memory-server';
describe('Auth', () => {
let app: INestApplication;
let mongoServer: MongoMemoryServer;
beforeAll(async () => {
// Set up database
mongoServer = new MongoMemoryServer();
const mongoUri = await mongoServer.getUri();
const moduleRef = await Test.createTestingModule({
imports: [
...
TypegooseModule.forRoot(mongoUri, {
useNewUrlParser: true,
useUnifiedTopology: true,
}),
...
],
controllers: [...],
providers: [...],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
afterAll(async () => {
await mongoServer.stop();
});
});
The main mistake I was making was that I had separately overwritten my User Typegoose injection.
providers: [
// THIS WAS MY MISTAKE!
{
provide: getModelToken('User'),
useValue: mockUserModel,
},
],
I know this is not a complete answer to your question, but I hope this might help you.