Mongo Memory Server: Property 'getUri' does not exist on type '(opts?: MongoMemoryServerOpts) => Promise<MongoMemoryServer>' - mongodb

I am trying to run e2e tests on a nestjs app.
I have trouble running MongoMemoryServer, in order to run set the MMS I used this article based on the original nestJs documentation.
I keep getting this error:
test/user-preferences.e2e-spec.ts:27:32 - error TS2339: Property 'getUri' does not exist on type '(opts?: MongoMemoryServerOpts) => Promise<MongoMemoryServer>'.
27 const uri = mongod.getUri();
~~~~~~
Test Suites: 1 failed, 1 total
This is the test I try to run:
import { Test, TestingModule } from '#nestjs/testing';
import { getModelToken, MongooseModule } from '#nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import {
UserPreferences,
UserPreferencesDocument,
UserPreferencesSchema,
} from './../src/user-preferences/schemas/user-preferences.schema';
import { UserPreferencesModule } from './../src/user-preferences/user-preferences.module';
import * as request from 'supertest';
import { factory } from 'fakingoose';
import { Model } from 'mongoose';
describe('userPreferences controller', () => {
let userPreferencesModel;
let app;
const UserPreferencesFactory = factory<UserPreferencesDocument>(
UserPreferencesSchema,
).setGlobalObjectIdOptions({ tostring: false });
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
await MongooseModule.forRootAsync({
useFactory: async () => {
const mongod = await MongoMemoryServer.create;
const uri = mongod.getUri();
return {
uri: uri,
};
},
}),
UserPreferencesModule,
],
}).compile();
app = moduleFixture.createNestApplication();
console.log('app: ', app);
userPreferencesModel = moduleFixture.get<Model<UserPreferencesDocument>>(
getModelToken(UserPreferences.name),
);
await app.init();
});
beforeEach(() => {
// populate the DB with 1 UserPreference using fakingoose
const mockUserPreferences = UserPreferencesFactory.generate();
return userPreferencesModel.create(mockUserPreferences);
});
afterEach(() => userPreferencesModel.remove({}));
it('GET /api/v1/user-preferences', () => {
return request(app.getHttpServer())
.get('/api/v1/user-preferences')
.expect(200)
.expect((res) => {
console.log('res: ', res);
expect(res.body.length > 0).toBe(true);
});
});
afterAll(() => {
app.close();
});
});
This is my schema file:
import { Schema, Prop, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export enum exitToOptions {
THIS_POST = 'this_post',
ALL_POSTS = 'all_posts',
DASHBOARD = 'dashboard',
}
export type UserPreferencesDocument = UserPreferences & Document;
#Schema()
export class UserPreferences {
#Prop({ unique: true })
eUserId: string;
#Prop()
uiTheme: string;
#Prop()
panelWidth: number;
#Prop()
editingHandles: boolean;
#Prop()
enableLightboxInEditor: boolean;
#Prop()
hiddenElements: boolean;
#Prop()
defaultDeviceView: string;
// #Prop()
// exitTo: exitToOptions
#Prop()
exitTo: string;
}
export const UserPreferencesSchema =
SchemaFactory.createForClass(UserPreferences);

Related

MissingSchemaError: Schema hasn't been registered for model in nextjs13

error - MissingSchemaError: Schema hasn't been registered for model "post".
Use mongoose.model(name, schema)
at Mongoose.model (/Users/mac/Practice/portfolio_projects/ai-image-generation/node_modules/mongoose/lib/index.js:549:13)
at eval (webpack-internal:///(api)/./src/lib/mongodb/models/post.ts:34:52)
at (api)/./src/lib/mongodb/models/post.ts (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:62:1)
at webpack_require (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/webpack-api-runtime.js:33:42)
at eval (webpack-internal:///(api)/./src/pages/api/post.ts:9:82)
at (api)/./src/pages/api/post.ts (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:82:1)
at webpack_require (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/webpack-api-runtime.js:33:42)
at webpack_exec (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:92:39)
at /Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:93:28
at Object. (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:96:3)
for db connection
import { MongoClient } from "mongodb";
if (!process.env.MONGODB_URI) {
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"');
}
const uri = process.env.MONGODB_URI;
const options = {};
let client;
let clientPromise: Promise<MongoClient>;
if (process.env.NODE_ENV === "development") {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options);
global._mongoClientPromise = client.connect();
}
clientPromise = global._mongoClientPromise;
} else {
// In production mode, it's best to not use a global variable.
client = new MongoClient(uri, options);
clientPromise = client.connect();
}
// Export a module-scoped MongoClient promise. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise;
**Post.tsx **
import * as mongoose from "mongoose";
import Joi from "joi";
type post = {
name: string;
prompt: string;
photo: string;
};
const PostSchema = new mongoose.Schema({
name: { type: String, required: true },
prompt: { type: String, required: true },
photo: { type: String, required: true },
});
function validatePost(data: post) {
const schema = Joi.object({
name: Joi.string().min(1).max(100).required(),
prompt: Joi.string().min(2).required(),
photo: Joi.string().min(0).required(),
});
return schema.validate(data);
}
const Post = mongoose.model("post") || mongoose.model("post", PostSchema);
export { validatePost };
export default Post;
**
Where i called post modal**
import type { NextApiRequest, NextApiResponse } from "next";
import clientPromise from "#/lib/mongodb/mongodb";
import { v2 as cloudinary } from "cloudinary";
import Post from "#/lib/mongodb/models/post";
import { validatePost } from "#/lib/mongodb/models/post";
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});
export const config = {
api: {
bodyParser: {
sizeLimit: "50mb",
},
responseLimit: false,
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
await clientPromise;
if (req.method === "GET") {
try {
const posts = await Post.find({});
res.status(200).json({ success: true, data: posts });
} catch (err) {
res.status(500).json({
success: false,
message: "Fetching posts failed, please try again",
});
}
} else if (req.method === "POST") {
try {
const { error } = validatePost(req.body);
if (error) return res.status(400).send(error.details[0].message);
const { name, prompt, photo } = req.body;
const photoUrl = await cloudinary.uploader.upload(photo);
const post = new Post({
name,
prompt,
photo: photoUrl.url,
});
const newPost = await post.save();
res.status(200).json({ success: true, data: newPost });
} catch (err) {
res.status(500).json({
success: false,
message: "Unable to create a post, please try again",
});
}
}
}

Mongoose Schema properties validation with Typescript NextJS

i am trying to save new document to mongo db, the Schema validation is not working for me, i am trying ti make required true, but i still can add new document without the required field.
this is my schema:
// lib/models/test.model.ts
import { Model, Schema } from 'mongoose';
import createModel from '../createModel';
interface ITest {
first_name: string;
last_name: string;
}
type TestModel = Model<ITest, {}>;
const testSchema = new Schema<ITest, TestModel>({
first_name: {
type: String,
required: [true, 'Required first name'],
},
last_name: {
type: String,
required: true,
},
});
const Test = createModel<ITest, TestModel>('tests', testSchema);
module.exports = Test;
this is createModel:
// lib/createModel.ts
import { Model, model, Schema } from 'mongoose';
// Simple Generic Function for reusability
// Feel free to modify however you like
export default function createModel<T, TModel = Model<T>>(
modelName: string,
schema: Schema<T>
): TModel {
let createdModel: TModel;
if (process.env.NODE_ENV === 'development') {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
// #ts-ignore
if (!global[modelName]) {
createdModel = model<T, TModel>(modelName, schema);
// #ts-ignore
global[modelName] = createdModel;
}
// #ts-ignore
createdModel = global[modelName];
} else {
// In production mode, it's best to not use a global variable.
createdModel = model<T, TModel>(modelName, schema);
}
return createdModel;
}
and this is my tests file:
import { connection } from 'mongoose';
import type { NextApiRequest, NextApiResponse } from 'next';
const Test = require('../../../lib/models/test.model');
import { connect } from '../../../lib/dbConnect';
const ObjectId = require('mongodb').ObjectId;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
switch (req.method) {
case 'POST': {
return addPost(req, res);
}
}
}
async function addPost(req: NextApiRequest, res: NextApiResponse) {
try {
connect();
// const { first_name, last_name } = req.body;
const test = new Test({
first_name: req.body.first_name,
last_name: req.body.last_name,
});
let post = await test.save();
// return the posts
return res.json({
message: JSON.parse(JSON.stringify(post)),
success: true,
});
// Erase test data after use
//connection.db.dropCollection(testModel.collection.collectionName);
} catch (err) {
//res.status(400).json(err);
res.status(400).json({
message: err,
success: false,
});
}
}
in the Postman, i send a request body without the required field (first_name) and i still can add it.
any help?

Access to files in MongoDB GridFS with NestJS and GraphQL

I'm working with NestJS with GraphQL and MongoDB.
I'm trying store image files using GridFS using mongo-gridfs package.
Uploading images to database works fine, but how can I access to this files?
I mean for example I want to get source path of this files and use it in my frontend
Here is my resolver and service:
// photo.resolver.ts
import { Resolver, Mutation, Query, Args } from '#nestjs/graphql';
import { GraphQLUpload, FileUpload } from 'graphql-upload';
import { Photo } from './dto/photo.dto';
import { PhotoService } from './services/photo.service';
#Resolver()
export class PhotoResolver {
constructor(
private readonly photoService: PhotoService
) {}
#Query(() => ???, { nullable: true })
async photo(#Args('id', { nullable: true }) id: string) {
const photo = await this.photoService.findOne(id);
return ???;
}
#Mutation(() => Photo)
async uploadPhoto(#Args({name: 'file', type: () => GraphQLUpload}) file: FileUpload) {
return await this.photoService.save(file);
}
}
// photo.service.ts
import { Connection } from 'mongoose';
import { Injectable } from '#nestjs/common';
import { InjectConnection } from '#nestjs/mongoose';
import { FileUpload } from 'graphql-upload';
import { MongoGridFS } from 'mongo-gridfs';
import { Photo } from '../photo.interface';
#Injectable()
export class PhotoService {
private fileModel: MongoGridFS;
constructor(
#InjectConnection() private readonly connection: Connection
) {
this.fileModel = new MongoGridFS(this.connection.db as any, 'photo');
}
async findOne(id: string) {
return await this.fileModel.findById(id);
}
async save(file: FileUpload): Promise<Photo> {
return await this.fileModel.writeFileStream(file.createReadStream(), {
filename: file.filename,
contentType: file.mimetype
});
}
}
I've tried two approached:
I used downloadFile method from my photoModel, but it returns path to this file in my Temp directory in local disk.
// photo.service.ts
async findOne(id: string): Promise<string> {
return await this.fileModel.downloadFile(id); // C:\...\AppData\Local\Temp\189450ef
}
// photo.resolver.ts
#Query(() => String, { nullable: true })
async photo(#Args('id', { nullable: true }) id: string) {
return id && await this.photoService.findOne(id);
}
It works per se but it doesn't look to me as a proper solution. I'd prefer that source path should "lead" to my server.
I used readFileStream method from my photoModel, which return filestream and added #Res() res to arguments in resolver.
// photo.service.ts
async findOne(id: string): Promise<GridFSBucketReadStream> {
return await this.fileModel.readFileStream(id);
}
// photo.resolver.ts
#Query(() => Boolean)
async photo(#Args('id', { nullable: true }) id: string, #Res() res) {
const photoStream = await this.photoService.findOne(id);
photoStream.pipe(res);
return true;
}
And now I've got such an error in terminal:
[Nest] 12408 - 10.07.2021, 13:02:25 [ExceptionsHandler] dest.on is not a function +27555ms
TypeError: dest.on is not a function

Problem calling mongoose service with nestjs in jest testing

Currently i'm trying to test my service but always fails and prints the error, even when the app is running correctly and the service is working
Error
TypeError: undefined is not a function
at Array.find (<anonymous>)
at NewsService.findAll (MY_ROUTE\src\news\news.service.ts:28:8)
at Object.it (MY_ROUTE\src\news\news.service.spec.ts:33:10)
at Object.asyncJestTest (MY_ROUTE\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:106:37)
at resolve (MY_ROUTE\node_modules\jest-jasmine2\build\queueRunner.js:45:12)
at new Promise (<anonymous>)
at mapper (MY_ROUTE\node_modules\jest-jasmine2\build\queueRunner.js:28:19)
at promise.then (MY_ROUTE\node_modules\jest-jasmine2\build\queueRunner.js:75:41)
at process._tickCallback (internal/process/next_tick.js:68:7)
at service.findAll.then.catch (news/news.service.spec.ts:39:19)
news.service.spec.ts
import { NewsService } from './news.service';
import { Model } from 'mongoose';
import { News, NewsSchema } from './schemas/news.schema';
import { getModelToken } from '#nestjs/mongoose';
describe('NewsService', () => {
let service: NewsService;
const mockRepository = (...args: any[]) => { };
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [NewsService, { provide: getModelToken(News.name), useFactory: mockRepository }],
}).compile();
service = module.get<NewsService>(NewsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('get news', () => {
it('should get all news', async () => {
service
.findAll()
.then((allNews) => {
console.log(allNews);
expect(allNews).toBeDefined();
})
.catch((error) => {
console.log(error);
});
});
});
});
news.schema.ts
import { Document } from 'mongoose';
export type NewsDocument = News & Document;
#Schema()
export class News extends Document {
#Prop({ unique: true })
id: string;
#Prop()
title: string;
#Prop()
date: string;
}
export const NewsSchema = SchemaFactory.createForClass(News);
news.service.ts
import { Model } from 'mongoose';
import { InjectModel } from '#nestjs/mongoose';
import { News } from './schemas/news.schema';
#Injectable()
export class NewsService {
constructor(
#InjectModel(News.name) private readonly newsModel: Model<News>
) {}
public async findAll(): Promise<News[]> {
return await this.newsModel
.find()
.sort([['date', 'descending']])
.exec();
}
}
I'm just learning about Jest, but after a lot of research and tests, i couldn't figured out what i'm doing wrong exactly.
EDIT
This is the only "decent" thing that i've tried to, but other errors appear. Maybe my whole focus on this is wrong.
const mockRepository = (...args: any[]) => {
findAll: jest.fn().mockReturnValue([
new News({
id: '1',
title: 'title',
date: 'date',
}),
]);
};
Error
TypeError: Cannot read property 'plugin' of undefined
5 |
6 | #Schema()
> 7 | export class News extends Document {
| ^
8 | #Prop({ unique: true })
9 | id: string;
10 |
at News.Object.<anonymous>.Document.$__setSchema (../node_modules/mongoose/lib/document.js:3028:10)
at new Document (../node_modules/mongoose/lib/document.js:86:10)
at new News (news/schemas/news.schema.ts:7:1)
at InstanceWrapper.mockRepository [as metatype] (news/news.service.spec.ts:13:7)
at Injector.instantiateClass (../node_modules/#nestjs/core/injector/injector.js:293:55)
at callback (../node_modules/#nestjs/core/injector/injector.js:77:41)

AccountsJs TypeORM

I have a bit of a problem with account and TypeORM
I've configured everything and there are no errors, but I can't seem to create users, I run the mutation but it doesn't fail and it runs in like 2ms, but when I go to the DB (MongoDB Atlas) I don't see the user collection and there are no users created.
Here is my config:
import { AccountsModule } from "#accounts/graphql-api";
import { AccountsServer } from "#accounts/server";
import { AccountsPassword } from "#accounts/password";
import AccountsTypeorm from "#accounts/typeorm";
import { Connection } from "typeorm";
export const setUpAccounts = async (connection: Connection) => {
const db = new AccountsTypeorm({ connection, cache: 1000 });
const password = new AccountsPassword({
twoFactor: {
appName: "Events"
}
});
const accountsServer = new AccountsServer(
{
db,
tokenSecret: "terrible secret",
siteUrl: "http://localhost:3000"
},
{
password
}
);
const accounts = AccountsModule.forRoot({
accountsServer,
headerName: "x-events-token"
});
return accounts;
};
index file:
require("dotenv").config();
import "reflect-metadata";
import express, { Application } from "express";
import { ApolloServer, makeExecutableSchema } from "apollo-server-express";
import { mergeTypeDefs } from "graphql-toolkit";
import { buildTypeDefsAndResolvers } from "type-graphql";
import { resolvers } from "./graphql";
import { connectDatabase } from "./db";
import { setUpAccounts } from "./lib/accounts";
import { CategoryResolver } from "./modules/Category/CategoryResolver";
const port = process.env.PORT;
const corsOptions = {
origin: "http://localhost:3000",
credentials: true
};
const mount = async (app: Application): Promise<void> => {
const connection = await connectDatabase();
const accounts = await setUpAccounts(connection);
const schema = await buildTypeDefsAndResolvers({
resolvers: [resolvers, CategoryResolver, accounts.resolvers]
});
const server = new ApolloServer({
schema: makeExecutableSchema({
typeDefs: mergeTypeDefs([accounts.typeDefs, schema.typeDefs]),
resolvers: schema.resolvers,
schemaDirectives: {
...accounts.schemaDirectives
}
}),
context: accounts.context,
playground: true
});
server.applyMiddleware({
app: app,
path: "/graphql",
cors: corsOptions
});
app.listen(port, () => {
console.log(`[ app ]: running on http://localhost:${port}`);
});
};
mount(express());
connectDatabase:
import { createConnection, Connection } from "typeorm";
import path from "path";
const user = process.env.DB_USER;
const userPassword = process.env.DB_USER_PASSWORD;
const cluster = process.env.DB_CLUSTER;
const uri = `mongodb+srv://${user}:${userPassword}#${cluster}.mongodb.net/`;
export const connectDatabase = async (): Promise<Connection> => {
const connection = await createConnection({
url: uri,
type: "mongodb",
database: "main",
w: "majority",
useNewUrlParser: true,
useUnifiedTopology: true,
logger: "advanced-console",
logging: "all",
entities: [
path.join(__dirname, "/../entities/**/*.*"),
...require("#accounts/typeorm").entities
]
});
console.log(connection.options.entities);
return connection;
};
Nvm I fixed it by moving accounts.resolvers from buildTypeDefsAndResolvers to makeExecutableSchema:
schema: makeExecutableSchema({
typeDefs: mergeTypeDefs([accounts.typeDefs, schema.typeDefs]),
resolvers: mergeResolvers([schema.resolvers, accounts.resolvers]),
schemaDirectives: {
...accounts.schemaDirectives
}
})
Works now :)