I'm parsing all the modules in a directory, initialise them, and finally export them. The modules in question are knex models like this one:
// schema/users.js
import createModel from './common'
const name = 'User'
const tableName = 'users'
const selectableProps = ['userId', 'name', 'email', 'updated_at', 'created_at']
export default knex => {
const model = createModel({
knex,
name,
tableName,
selectableProps,
})
return { ...model }
}
Each of the model definitions is extended with common parts:
// schema/common.js
export default ({
knex = {},
name = 'name',
tableName = 'tablename',
selectableProps = [],
timeout = 1000,
}) => {
const findAll = () =>
knex
.select(selectableProps)
.from(tableName)
.timeout(timeout)
return {
name,
tableName,
selectableProps,
timeout,
findAll,
}
}
Finally, all models are initialised and exported:
// schema/index.js
import { readdir } from 'fs'
import { promisify } from 'util'
import { default as knex } from '../db'
let modules = {}
export default new Promise(async $export => {
const readFileAsync = promisify(readdir)
const getModels = async dir => await readFileAsync(dir)
const files = await getModels(__dirname)
for await (const file of files) {
if (file !== 'common.js' && file !== 'index.js') {
let mod = await import(__dirname + '/' + file).then(m => m.default(knex))
modules[mod.name] = mod
}
}
await $export(modules)
console.log(modules)
})
The above seem to be working but I'm not able to figure out how to import one of these modules from another file. I'm trying to do something along the lines of:
const User = async () => await import('../schema') // not working
or
const User = (async () => await import('../schema'))() // not working
Any help with this will be appreciated!
Related
I am not getting any clue how to mock a method. I have to write a unit test for this function:
index.ts
export async function getTenantExemptionNotes(platform: string) {
return Promise.all([(await getCosmosDbInstance()).getNotes(platform)])
.then(([notes]) => {
return notes;
})
.catch((error) => {
return Promise.reject(error);
});
}
api/CosmosDBAccess.ts
import { Container, CosmosClient, SqlQuerySpec } from "#azure/cosmos";
import { cosmosdbConfig } from "config/Config";
import { Workload } from "config/PlatformConfig";
import { fetchSecret } from "./FetchSecrets";
export class CosmoDbAccess {
private static instance: CosmoDbAccess;
private container: Container;
private constructor(client: CosmosClient) {
this.container = client
.database(cosmosdbConfig.database)
.container(cosmosdbConfig.container);
}
static async getInstance() {
if (!CosmoDbAccess.instance) {
try {
const connectionString = await fetchSecret(
"CosmosDbConnectionString"
);
const client: CosmosClient = new CosmosClient(connectionString);
// Deleting to avoid error: Refused to set unsafe header "user-agent"
delete client["clientContext"].globalEndpointManager.options
.defaultHeaders["User-Agent"];
CosmoDbAccess.instance = new CosmoDbAccess(client);
return CosmoDbAccess.instance;
} catch (error) {
// todo - send to app insights
}
}
return CosmoDbAccess.instance;
}
public async getAllNotesForLastSixMonths() {
const querySpec: SqlQuerySpec = {
// Getting data from past 6 months
query: `SELECT * FROM c
WHERE (udf.convertToDate(c["Date"]) > DateTimeAdd("MM", -6, GetCurrentDateTime()))
AND c.IsArchived != true
ORDER BY c.Date DESC`,
parameters: [],
};
const query = this.container.items.query(querySpec);
const response = await query.fetchAll();
return response.resources;
}
}
export const getCosmosDbInstance = async () => {
const cosmosdb = await CosmoDbAccess.getInstance();
return cosmosdb;
};
index.test.ts
describe("getExemptionNotes()", () => {
beforeEach(() => {
jest.resetAllMocks();
});
it("makes a network call to getKustoResponse which posts to axios and returns what axios returns", async () => {
const mockNotes = [
{
},
];
const cosmosDBInstance = jest
.spyOn(CosmoDbAccess, "getInstance")
.mockReturnValue(Promise.resolve(CosmoDbAccess.instance));
const kustoResponseSpy = jest
.spyOn(CosmoDbAccess.prototype, "getAllNotesForLastSixMonths")
.mockReturnValue(Promise.resolve([mockNotes]));
const actual = await getExemptionNotes();
expect(kustoResponseSpy).toHaveBeenCalledTimes(1);
expect(actual).toEqual(mockNotes);
});
});
I am not able to get instance of CosmosDB or spyOn just the getAllNotesForLastSixMonths method. Please help me code it or give hints. The complexity is because the class is singleton or the methods are static and private
I have the following content structure:
src
|-- content
|-- terms-conditions.html
|-- makeup.html
The terms and conditions page has the following data which I can call
{ seo:
url: "makeup/terms-conditions"
}
I'd like the content of terms-conditions.html to actually show under the url makeup/terms-conditions. I've tried a few times using getStaticPaths to no avail so any guidance would be appreciated. Essentially all of my files will sit one level down from content but I'd like to actually serve them under the URL defined under seo/url
My getStaticPaths code in my [params].tsx file is as follows.
export async function getStaticPaths() {
const posts = getAllPosts(["slug"]);
return {
paths: posts.map((post) => {
return {
params: {
slug: post.slug,
},
};
}),
fallback: false,
};
}
getStaticProps:
export async function getStaticProps({ params }) {
const post = getPostBySlug(params.slug, ["slug", "data"]);
const content = (await post.content) || "";
return {
props: {
post: {
...post,
content,
},
},
};
}
And in my api file I have:
import fs from "fs";
import { join } from "path";
import matter from "gray-matter";
const postsDirectory = join(process.cwd(), "src/content");
export function getPostSlugs() {
return fs.readdirSync(postsDirectory);
}
export function getPostBySlug(slug: string, fields = []) {
const realSlug = slug.replace(/\.html$/, "");
const fullPath = join(postsDirectory, `${realSlug}.html`);
const fileContents = fs.readFileSync(fullPath, "utf8");
const { data, content } = matter(fileContents);
const items = { };
fields.forEach((field) => {
if (field === 'slug') {
items[field] = realSlug
}
if (field === 'data') {
items[field] = data
}
})
return items;
}
export function getAllPosts(fields = []) {
const slugs = getPostSlugs()
const posts = slugs
.map((slug) => getPostBySlug(slug, fields))
return posts
}
After defining class CognitoPool saving it as cognitoPool.ts script:
const AWS = require('aws-sdk');
import { CognitoIdentityServiceProvider } from 'aws-sdk';
import {ListUsersRequest, ListUsersResponse} from 'aws-sdk/clients/cognitoidentityserviceprovider';
export class CognitoPool {
private identityService: CognitoIdentityServiceProvider;
constructor(identityService: CognitoIdentityServiceProvider) {
this.identityService = identityService;
}
async listCognitoUsers(poolID: string, sub: string): Promise<ListUsersResponse> {
let params = {
UserPoolId: poolID,
Filter: `sub="${sub}"`
} as ListUsersRequest;
let res: ListUsersResponse = await this.identityService.listUsers(params).promise();
return res;
}
}
export default new CognitoPool(new AWS.CognitoIdentityServiceProvider());
I go ahead and write a test script:
const AWS = require('aws-sdk');
import sinon, { stubObject } from 'ts-sinon'
import { CognitoIdentityServiceProvider, AWSError } from 'aws-sdk';
import { PromiseResult } from 'aws-sdk/lib/request';
import { CognitoPool } from './cognitoPool';
describe('Testing', () => {
const identityService = new AWS.CognitoIdentityServiceProvider();
const stub = stubObject(identityService);
const cognitoPool = new CognitoPool(stub);
it('Test 01', async () => {
let mockData = {
Users: []
} as unknown as PromiseResult<any, AWSError>;
stub.listUsers.returns(mockData);
let result = await cognitoPool.listCognitoUsers('poolId-123', 'sub-123');
})
})
A mockData is to be returned by identityService.listUsers() as as PromiseResult:
let mockData = {
Users: []
} as unknown as PromiseResult<any, AWSError>;
But a test script runs with an error:
TypeError: this.identityService.listUsers(...).promise is not a function
Is there a way to avoid this error?
PromiseResult is an object that includes .promise as a function. Then, when you want to mock a function to return a PromiseResult, the mock data should be an object like PromiseResult.
In your case, mockData should be:
const mockData = {
promise: () => Promise.resolve({ Users: [] }),
} as unknown as PromiseResult<any, AWSError>;
The following code constructs a redis client and exports. I am fetching the redis password from vault secret management service and that call is a promise/async. The code doesnt wait for that call and it exports the redis client before async call completes. I am not sure what I am doing wrong here. Any idea?
import redis from 'redis';
import bluebird from 'bluebird';
import logger from '../logger';
import srvconf from '../srvconf';
import { getVaultSecret } from '../services/vault.service';
const vaultConfig = srvconf.get('vault');
bluebird.promisifyAll(redis);
let redisUrl = '';
const maskRedisUrl = (url) => url.replace(/password=.*/, 'password=*****');
const setRedisUrl = (host, port, pw) => {
const pwstring = pw ? `?password=${pw}` : '';
const url = `redis://${host}:${port}${pwstring}`;
console.log(`Setting redis_url to '${maskRedisUrl(url)}'`);
return url;
}
if (vaultConfig.use_vault) {
(async () => {
const secret = await getVaultSecret(`${vaultConfig.redis.secrets_path + vaultConfig.redis.key}`)
redisUrl = setRedisUrl(srvconf.get('redis_host'), srvconf.get('redis_port'), secret.PASSWORD);
})().catch(err => console.log(err));
} else {
if (!srvconf.get('redis_url')) {
redisUrl = setRedisUrl(srvconf.get('redis_host'), srvconf.get('redis_port'), srvconf.get('redis_password'));;
} else {
redisUrl = srvconf.get('redis_url');
console.log(`Found redis_url ${maskRedisUrl(redisUrl)}`);
}
}
const options = redisUrl
? { url: redisUrl }
: {};
const redisClient = redis.createClient(options);
redisClient.on('error', err => {
logger.error(err);
});
export default redisClient;
The problem is that (async () => {...})() returns a Promise and you are not awaiting it at the top-level, so the script continues to run past that line, sets options = {} and returns the redisClient.
What you need is a top-level await which is enabled by default in Node versions >= 14.8.0. However, if your project uses a version older than that, there is a workaround as shown below.
Please note that the below code is NOT tested since I do not have the same project setup locally.
Module
import redis from "redis";
import bluebird from "bluebird";
import logger from "../logger";
import srvconf from "../srvconf";
import { getVaultSecret } from "../services/vault.service";
const vaultConfig = srvconf.get("vault");
bluebird.promisifyAll(redis);
let redisUrl = "";
let redisClient = null;
const initRedisClient = () => {
const options = redisUrl ? { url: redisUrl } : {};
redisClient = redis.createClient(options);
redisClient.on("error", (err) => {
logger.error(err);
});
};
const maskRedisUrl = (url) => url.replace(/password=.*/, "password=*****");
const setRedisUrl = (host, port, pw) => {
const pwstring = pw ? `?password=${pw}` : "";
const url = `redis://${host}:${port}${pwstring}`;
console.log(`Setting redis_url to '${maskRedisUrl(url)}'`);
return url;
};
(async () => {
if (vaultConfig.use_vault) {
try {
const secret = await getVaultSecret(
`${vaultConfig.redis.secrets_path + vaultConfig.redis.key}`
);
redisUrl = setRedisUrl(
srvconf.get("redis_host"),
srvconf.get("redis_port"),
secret.PASSWORD
);
} catch (err) {
console.log(err);
}
} else {
if (!srvconf.get("redis_url")) {
redisUrl = setRedisUrl(
srvconf.get("redis_host"),
srvconf.get("redis_port"),
srvconf.get("redis_password")
);
} else {
redisUrl = srvconf.get("redis_url");
console.log(`Found redis_url ${maskRedisUrl(redisUrl)}`);
}
}
// Initialize Redis client after vault secrets are loaded
initRedisClient();
})();
export default redisClient;
Usage
At all places where you import and use the client, you always need to check if it is actually initialized successfully, and throw (and catch) a well defined error if it is not.
const redisClient = require("path/to/module");
...
if (redisClient) {
// Use it
} else {
throw new RedisClientNotInitializedError();
}
...
I am trying to implement separation of concerns by using export module. All the code is working if used without separation of concern but as soon as I am trying to import generateUrlArray() from const db = require('../db') nothing is working. Nodejs is not giving me any error on the back-end. The error I am getting on front-end is Error: SyntaxError: Unexpected end of JSON input . I am positive that the error is coming from back-end. Let me know if you have any ideas.
controller.js
const db = require('../db')
exports.getWebApiList = (req, res) => {
(async function fetchDataList() {
try {
const urlArray = await db.generateUrlArray({}, { _id: 0 })
return res.send(urlArray)
} catch (ex) {
console.log(`fetchDataList error: ${ex}`)
}
})()
}
..db/index.js
const { List } = require('./models/List')
const generateUrlArray = (query, projection) => {
const dataFromDB = List.find(query, projection).select('symbol')
return linkArray = dataFromDB.map(item => {
return link = `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${item.symbol}&apikey=6BUYSS9QR8Y9HH15`
})
}
module.exports = { generateUrlArray }
.models/List.js
const mongoose = require('mongoose')
mongoose.Promise = global.Promise
const ParentSchemaSymbolList = new mongoose.Schema({
symbol: String
})
module.exports.List = mongoose.model('List', ParentSchemaSymbolList)
const generateUrlArray = async (query, projection) => {
const dataFromDB = await List.find(query, projection).select('symbol')
const linkArray = dataFromDB.map(item => {
return link = `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${item.symbol}&apikey=6BUYSS9QR8Y9HH15`
})
return linkArray
}