Unit Testing for dynamodb-onetable - javascript

I am new to Unit Testing and wanted to stub dynamodb-onetable library. I was trying to stub getData() from getDetails.ts file but it shows that "OneTableArgError: Missing Name Property". Because this getProjectDetails() contain new Table() class.
How to stub dynamodb-onetable so that I can get data in dataDetails variable. I was doing something like this in getEmp.spec.ts
dataDetailsStub = sinon.stub(DataService , "getData");
------lambda.ts
import { DynamoDBClient } from '#aws-sdk/client-dynamodb';
import Dynamo from 'dynamodb-onetable/Dynamo';
export const client = new Dynamo({
client: new DynamoDBClient({
region: REGION, }),
});
-------DataService.ts
import { client } from '../lambda';
const workspaceTable = new Table({
client,
name: TableName,
schema,
logger: true,
partial: false,
});
const projectDetail = workspaceTable.getModel('empDetail');
export default class **DataService** {
static getData = async (empId: string, type: string) => {
const params = {
projectId,
type
};
const response = await empDetail.find(params);
logger.trace('response', { response });
return response; };
}
------getDetails.ts
const dataDetails= await DataService.getData(
empId,
'EMPLOYEE-SAVEDATA'
);
I was trying to stub the DataService.getData() but getting error saying "OneTableArgError: Missing "name" property". I want to get data in dataDetailsStub whatever i am sending while mocking the getData()
const dataDetailsStub = sinon.stub(DataService , "getData").return({emp object});
Can anyone help me out on this. I'm really got stuck in this. Thanks in advance

Related

GraphQL - Cannot move resolver to a separate file

I have searched the forum for anything related to customizing GraphQL in Strapi v4 but found nothing.
Note: my GraphQL skills is a novice.
I extended my GraphQL resolver in Strapi v4, and it worked fine as long as the resolver was in the same file as "index.ts." I want to modularize my GraphQL code by moving the resolver into a separate file. When I did that, I kept getting the following error:
"resolvers" is defined in the resolver but not in the schema.
Below is my resolver embedded in the file index.ts, which works fine without any issue.
index.ts
/**
* Extend register for GraphQL
*/
register({ strapi }): void {
// customized programmatically using GraphQL's extension
const extensionService = strapi.plugin("graphql").service("extension");
const UID = "api::truth-lending-disclosure.truth-lending-disclosure";
extensionService.use(({ strapi }) => ({
typeDefs: ``,
resolvers: {
Query: {
truthLendingDisclosures: async (parent, args, context) => {
// toEntityResponse method to allow us to convert our response
// to the appropriate format before returning the data.
const { toEntityResponseCollection } = strapi
.plugin("graphql")
.service("format").returnTypes;
// define level to populate
let _populate = {
body: {
populate: {
section: true,
},
},
};
// using shadow CRUD from entity service to fetch data
let entities = await strapi.entityService.findMany(UID, {
populate: _populate,
});
// find and replace placeholder with key-value risCustomerTermDataMap
// return the result as JSON string
let stringResult = dataSubstitution(
JSON.stringify(entities), // convert an object to JSON string
risCustomerTermDataMap
);
// conver JSON string back object
let objectResult = JSON.parse(stringResult);
debugger;
return toEntityResponseCollection(objectResult);
},
},
},
}));
},
Moved the resolvers logic into a separate file mycustom.resolvers.ts
mycustom.resolvers.ts
import { risCustomerTermDataMap } from "../../../../libs/common/risaCustomerTermDataMap";
import { dataSubstitution } from "../../../../libs/helpers/dataSubstitution";
// the logic in this file does not work with index.ts yet
// keep getting "resolvers" define in resolvers, but not in schema.
export const resolvers = {
Query: {
truthLendingDisclosures: async (parent, args, context) => {
console.log("***** GraphQL Resolvers*****");
const UID = "api::truth-lending-disclosure.truth-lending-disclosure";
// toEntityResponse method to allow us to convert our response
// to the appropriate format before returning the data.
const { toEntityResponseCollection } = strapi
.plugin("graphql")
.service("format").returnTypes;
// define level to populate
let _populate = {
body: {
populate: {
section: true,
},
},
};
// using shadow CRUD from entity service to fetch data
let entities = await strapi.entityService.findMany(UID, {
populate: _populate,
});
// find and replace placeholder with key-value risCustomerTermDataMap
// return the result as JSON string
let stringResult = dataSubstitution(
JSON.stringify(entities), // convert an object to JSON string
risCustomerTermDataMap
);
// conver JSON string back object
let objectResult = JSON.parse(stringResult);
debugger;
return toEntityResponseCollection(objectResult);
},
},
};
What am I missing?
Finally,I got it to work.
I modified my resolver in the external file "mycustom.resolvers.ts" to this:
filename: mycustom.resolvers.ts
export const truthLendingDisclosureResolvers = {
Query: {
async truthLendingDisclosures(): Promise<any> {
console.log("***** external resolver *****");
const UID = "api::truth-lending-disclosure.truth-lending-disclosure";
// toEntityResponse method to allow us to convert our response
// to the appropriate format before returning the data.
const { toEntityResponseCollection } = strapi
.plugin("graphql")
.service("format").returnTypes;
// define level to populate
let _populate = {
body: {
populate: {
section: true,
},
},
};
// using shadow CRUD from entity service to fetch data
let entities = await strapi.entityService.findMany(UID, {
populate: _populate,
});
// find and replace placeholder with key-value risCustomerTermDataMap
// return the result as JSON string
let stringResult = dataSubstitution(
JSON.stringify(entities), // convert an object to JSON string
risCustomerTermDataMap
);
// conver JSON string back object
let objectResult = JSON.parse(stringResult);
return toEntityResponseCollection(objectResult, {
args: {},
resourceUID: UID,
});
},
},
};
and modified the "index.ts" to this:
filename: index.ts
register({ strapi }): void {
// customized programmatically using GraphQL's extension
const extensionService = strapi.plugin("graphql").service("extension");
const UID = "api::truth-lending-disclosure.truth-lending-disclosure";
// disable an action on a query
// extensionService.shadowCRUD(UID).disableAction("find");
extensionService.use(({ strapi }) => ({
typeDefs: ``,
resolvers: truthLendingDisclosureResolvers,
}));
},

multiple url variable async/await axios

Probably this is a very stupid question, i'm new in Node.js and javascript, so please forgive me if the question is not properly explained or the answer is simple...
I'm trying to send 2 variables thru a url... When i send only 1 variable (artist=${term}) work all good, but I'm really stuck with about how to send 2 variables thru the url (&artist=${term1}&album=${term2})
I've work on this code so far which for 1 variable is working well... but i have no idea how to add a second or a third variable to the request:
File 1: "./services/albumInfo.js"
import { BRV_API } from '../../config';
import axios from 'axios';
import dotenv from 'dotenv';
const ALBUM_INFO = 'method=album.getinfo';
dotenv.config();
const doRequest = async (url) => {
return await axios.get(`${BRV_API}/${url}&api_key=${process.env.API_KEY}&format=json`);
};
export const infoAlbum = async (term) => {
return await doRequest(`?${ALBUM_INFO}&artist=${term}`);
};
File 2: "./repositories/albumInfo.js"
import { infoAlbum } from '../repositories/albumInfo';
import status from 'http-status';
export const albumInfo = async (req, res, next) => {
try {
const { query } = req;
const { data } = await infoAlbum(query.name);
const response = data;
res.status(status.OK).send(response);
} catch (error) {
next(error);
}
};
I know that my problem is in this part of the code (I guess)
export const infoAlbum = async (term) => {
return await doRequest(`?${ALBUM_INFO}&artist=${term1}&album=${term2}`);
};
I've been searching, and i've seen some solution, like this one, but i just don't understand those solutions or how to apply on my code (sorry for that, im a very new on this)
Any good soul who can help this newbie? (if can explain the why of the solution as well, for understand, will be amazing!!)
Thanks in advance!!
Axios provides parameters that can be added custom as the following
const your_url = process.env.URL
const infoAlbum = await axios.get(your_url,{
params: {
artist: term,
album: term2,
api_key: process.env.API_KEY,
format:'json'
}
})
console.log(infoAlbum.data.args)
note: your_url without any more parameters.
So,
I've found a solution, which is pretty ugly, but so far is working, if someone have a better option, will be amazing to know:
File 1: repositories/albumInfo.js, I've just add the console (as per #Alex028502 suggestion), to know what the code was returning:
import { BRV_API } from '../../config';
import axios from 'axios';
import dotenv from 'dotenv';
const ALBUM_INFO = 'method=album.getinfo';
dotenv.config();
const doRequest = async (url) => {
const fullurl = `${BRV_API}/?${ALBUM_INFO}${url}&api_key=${process.env.API_KEY}&format=json`;
console.log('full url is', fullurl);
return await axios.get(fullurl);
};
export const infoAlbum = async (term) => {
return await doRequest(`&${term}`);
};
File 1: services/albumInfo.js: I change the behaviour of 'infoAlbum' to make the request from his side:
import { infoAlbum } from '../repositories/albumInfo';
import status from 'http-status';
export const albumInfo = async (req, res, next) => {
try {
const { query } = req;
console.log(query);
const { data } = await infoAlbum('artist=' + query.artist + '&album=' + query.album);
const response = data;
res.status(status.OK).send(response);
} catch (error) {
next(error);
}
};
I know that probably this is not the very best way to walk away from the problem, but so far is what i have.... any other better option about how to capture the second or third parameter of the url request and then add them to the final url?
Best!

Injected function into a class constructor throws undefined

I am learning about using typescript to build API's, I have come across two issues right now. First, I have a somewhat generic PostController Class that can accept a use-case that implements the PostMethod Interface, e.g
export interface PostMethod {
add: (req: Request, res: Response) => Promise<any> // not sure if it should be returning any
}
That's the interface, and the generic controller looks like this.
export class PostController implements PostMethod {
constructor(public postMethod: any) {}
async add(req: Request, res: Response) {
let { ...incomingHttpBody } = req.body
console.log('body', incomingHttpBody)
console.log(this.postMethod)
type Source = {
ip: string
browser: string | string[] | undefined
referrer: string | string[]
}
let source = {} as Source
source.ip = req.ip
source.browser = req.headers['User-Agent']
if (req.headers.Referer) {
source.referrer = req.headers.Referer
}
const newItem = await this.postMethod({ source, ...incomingHttpBody })
return apiResponse({
status: true,
statusCode: 201,
message: 'Resource created successfully',
data: [newItem]
})
}
}
And then, I can use this PostController class like this
...
const postMethod = new AddUser(UsersDb).addUser
export const postUser = new PostController(postMethod)
...
The AddUser class looks like this,
export class AddUser {
constructor(public usersDb: UserDatabase) {}
async addUser(userInfo: IUser) {
console.log({...userInfo})
const exists = await this.usersDb.findByEmail(userInfo.email)
if (exists) {
throw new UniqueConstraintError('Email address')
}
const user = new UserFactory(userInfo)
user.makeUser()
const { email, ...details } = user.user
const newUser = await this.usersDb.insert({ email, ...details })
const id = newUser.user._id
await createWallet(id)
// await publisher(id.toString(), 'newuser.verify')
// await consumer('verify_queue', verifyUser, '*.verify')
return newUser
}
}
When I do a console.log of req.body, I get the incoming body, but I keep getting TypeError: Cannot read property 'postMethod' of undefined. I am unsure how to annotate the constructor function also. I do not know what I could be doing wrong, when I console.log postUser, I do see the function passed as argument logged to the console, but when I try sending requests, it fails.
Please help, thank you.
I think there are some misconceptions here.
You're defining an interface PostMethod, which itself IS NOT a method. It is an interface. So if you try to pass an instance of this, please don't just pass a FUNCTION (or method). But pass an instance of PostMethod.
It could look like this (look at the changes I made):
export interface PostMethod {
add: (req: Request, res: Response) => Promise<any> // not sure if it should be returning any
}
export class PostController implements PostMethod {
constructor(public postMethod: any) {}
async add(req: Request, res: Response) {
// cloning? if so, do it like this
let incomingHttpBody = { ...req.body }
console.log('body', incomingHttpBody)
console.log(this.postMethod)
type Source = {
ip: string
browser: string | string[] | undefined
referrer: string | string[]
}
let source = {} as Source
source.ip = req.ip
source.browser = req.headers['User-Agent']
if (req.headers.Referer) {
source.referrer = req.headers.Referer
}
// change: I made a change here, you have to call postMethod.add!
const newItem = await this.postMethod.add({ source, ...incomingHttpBody })
return apiResponse({
status: true,
statusCode: 201,
message: 'Resource created successfully',
data: [newItem]
})
}
}
// change: instantiate AddUser which in turn implements PostMethod that can be passed...
const postMethod = new AddUser(UsersDb)
export const postUser = new PostController(postMethod)
// change: implementing PostMethod!
export class AddUser implements PostMethod {
constructor(public usersDb: UserDatabase) {}
// change: new method! this is a must, because we're implementing PostMethod!
async add(req: Request, res: Response) {
// this gets called, not addUser!
}
async addUser(userInfo: IUser) {
const exists = await this.usersDb.findByEmail(userInfo.email)
if (exists) {
throw new UniqueConstraintError('Email address')
}
const user = new UserFactory(userInfo)
user.makeUser()
const { email, ...details } = user.user
const newUser = await this.usersDb.insert({ email, ...details })
const id = newUser.user._id
await createWallet(id)
// await publisher(id.toString(), 'newuser.verify')
// await consumer('verify_queue', verifyUser, '*.verify')
return newUser
}
}
You need to instantiate the class first before you can use the method...
...
const postMethod = new AddUser(UsersDb)
//then use it
postMethod.addUser()
export const postUser = new PostController(postMethod)
...
However you could make it static so you do not need to instantiate it:
static async add(req: Request, res: Response) { ....
An function is usually written like this in an interface:
export interface PostMethod {
constructor(): void,
add(req: Request, res: Response): Promise<any>
}
I fixed the issue, after much digging and reading up I realized it was a problem with this losing its context. Hence why I was getting undefined when I do something like this.
const postMethod = new PostController(usecase)
The fix was to bind this in the constructor like so,
this.add = this.add.bind(this)
that fixed the issue as when the class was instantiated it doesn't just point to the methods of the class but also the associations of the class.
The postMethod parameter which is being passed into the constructor of PostMethod is not being stored anywhere. Store it ins a private class variable and then use the private class variable.
like -
const _postMethod = postMethod;
and then while calling call
const newItem = await this._postMethod({ source, ...incomingHttpBody })

#middy/http-json-body-parser can't parsing JSON

Hello I want parsing a JSON so I used middy middleware to take about it. But it won't work. The data which need parsing can't processing to dynamodb. I was install #middy/http-json-body-parser, #middy/http-event-normalizer, #middy/http-error-handler but still won't work. Here is my code.
import AWS from 'aws-sdk';
import middy from '#middy/core';
import httpJsonBodyParser from '#middy/http-json-body-parser';
import httpEventNormalizer from '#middy/http-event-normalizer';
import httpErrorHandler from '#middy/http-error-handler';
import createError from 'http-errors';
const nid = require('nid')({HEX:1, length:16})
const dynamodb = new AWS.DynamoDB.DocumentClient();
async function createAuction(event, context) {
const { title, description } = event.body;
const now = new Date();
const auction = {
id: nid(),
title,
description,
status: 'OPEN',
createdAt: now.toISOString(),
};
try {
await dynamodb.put({
TableName: process.env.AUCTIONS_TABLE_NAME,
Item: auction,
}).promise();
} catch(error) {
console.error(error);
throw new createError.InternalServerError(error);
}
return {
statusCode: 201,
body: JSON.stringify(auction),
};
}
export const handler = middy(createAuction)
.use(httpJsonBodyParser())
.use(httpEventNormalizer())
.use(httpErrorHandler());
Here is screenshot in result.
Issue has nothing to do with dynamodb. title and description aren't event parsed by middy most likely because the request is not content-type application/json or api gateway is not configured properly. Impossible to say with so little information, but you should console.log(event) just after async function createAuction(event, context) { and see if it does contain anything useful from your request.

How to execute GraphQL query from server

I am using graphql-express to create an endpoint where I can execute graphql queries in. Although I am using Sequelize with a SQL database it feels wrong to use it directly from the server outside of my graphql resolve functions. How do I go about querying my graphql API from the same server as it was defined in?
This is how I set up my graphql endpoint:
const express = require('express');
const router = express.Router();
const graphqlHTTP = require('express-graphql');
const gqlOptions = {
schema: require('./schema')
};
router.use('/', graphqlHTTP(gqlOptions));
modules.exports = router;
Basically what I want is to be able to do something like this:
query(`
{
user(id: ${id}) {
name
}
}
`)
How would I create this query function?
GraphQL.js itself does not require a http server to run. express-graphql is just a helper to mount the query resolver to a http endpoint.
You can pass your schema and the query to graphql, it'll return a Promise that'll resolve the query to the data.
graphql(schema, query).then(result => {
console.log(result);
});
So:
const {graphql} = require('graphql');
const schema = require('./schema');
function query (str) {
return graphql(schema, str);
}
query(`
{
user(id: ${id}) {
name
}
}
`).then(data => {
console.log(data);
})
I would like to complete the answer from #aᴍɪʀ by providing the pattern for properly doing a query / mutation with parameters:
const params = {
username: 'john',
password: 'hello, world!',
userData: {
...
}
}
query(`mutation createUser(
$username: String!,
$password: String!,
$userData: UserInput) {
createUserWithPassword(
username: $username,
password: $password,
userData: $userData) {
id
name {
familyName
givenName
}
}
}`, params)
This way, you don't have to deal with the string construction bits " or ' here and there.
Thanks for the other answers, this is for Nextjs inside getServerSideProps, getStaticProps, getStaticPaths and getStaticProps, includes context for MongoDB. Need this because if you have your graphql sever in api route, when you build it wont build because your server in api route is not running.
Mongo file: plugin/zDb/index:
import {MongoClient} from "mongodb"
export const connectToDatabase = async() => {
const client = new MongoClient(process.env.MONGODB_URI, {useNewUrlParser: true, useUnifiedTopology: true})
let cachedConnection
if(cachedConnection) return cachedConnection
try {
const connection = await client.connect()
cachedConnection = connection
return connection
} catch(error) {
console.error(error)
}
}
export const mongoServer = async() => {
const connect = await connectToDatabase()
return connect.db(process.env.DB_NAME)
}
In pages folder, eg index.js file homepage:
import {graphql} from 'graphql'
import {schema} from '#/plugin/zSchema/schema'
import {mongoServer} from '#/plugin/zDb/index'
async function query(source, variableValues) {
return graphql({schema, source, contextValue: {mongo: await mongoServer()}, variableValues})
}
export async function getServerSideProps(ctx) {
const listingCurrent = await query(`query($keyField: String, $keyValue: String) {
ListingRQlistingListKeyValue(keyField: $keyField, keyValue: $keyValue) {
address
urlSlug
imageFeature {
photoName
}
}
}`, {
keyField: 'offerStatus'
, keyValue: 'CURRENT'
})
return {props: {
listingCurrent: listingCurrent.data.ListingRQlistingListKeyValue
}
}
}
Please note: the graphql call field names is from: https://github.com/graphql/graphql-js/blob/fb27b92a5f66466fd8143efc41e1d6b9da97b1f4/src/graphql.js#L62
export type GraphQLArgs = {|
schema: GraphQLSchema,
source: string | Source,
rootValue?: mixed,
contextValue?: mixed,
variableValues?: ?ObjMap<mixed>,
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>,
|};
And my schema file: plugin/zSchema/schema.js
import { makeExecutableSchema } from '#graphql-tools/schema'
import {resolvers} from '#/plugin/zSchema/resolvers'
import {typeDefs} from '#/plugin/zSchema/typeDefs'
export const schema = makeExecutableSchema({resolvers, typeDefs})
The #/plugin folder: I'm using this in root file called jsconfig.json, and I put all my folders inside root/plugin, and I call it with #/plugin. You can use your own folder structure importing them as how you normally do it.
{
"compilerOptions": {
"baseUrl": "."
, "paths": {
"#/*": ["./*"]
}
}
}

Categories