I am trying to create a "Firm" microservice that registers a firm and users within the firm before a user can sign up for the web application. I am running tests on my route handlers and getting an error of "Property 'id' does not exist on type 'Promise'". Where am I going wrong?
When I mark the global.signin function as async and add await buildFirm()
as well as replace return [`session=${base64}`];
with ...
const cookie = [`session=${base64}`];
return new Promise<string[]>((resolve, reject) => {
resolve(cookie);
})
the error on id goes away, but I get a new error on global.signin
saying "Type '() => Promise<string[]>' is not assignable to type '() => string[]'.
Type 'Promise<string[]>' is missing the following properties from type 'string[]': length, pop, push, concat, and 29 more."
setup.ts (test setup):
import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import jwt from "jsonwebtoken";
import { Firm, FirmDoc } from '../models/firm';
declare global {
var rootsignin: () => string[];
}
declare global {
var signin: () => string[];
}
jest.mock("../nats-wrapper");
let mongo: any;
beforeAll(async () => {
jest.setTimeout(10000)
process.env.JWT_KEY = "asdfasdf";
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const mongo = await MongoMemoryServer.create();
const mongoUri = mongo.getUri();
await mongoose.connect(mongoUri, {});
});
beforeEach(async () => {
jest.clearAllMocks();
const collections = await mongoose.connection.db.collections();
for (let collection of collections) {
await collection.deleteMany({});
}
});
afterAll(async () => {
if (mongo) {
await mongo.stop();
}
await mongoose.connection.close();
});
global.rootsignin = () => {
const payload = {
id: new mongoose.Types.ObjectId().toHexString(),
email: "[email protected]",
name: 'Root User',
root: true,
};
const token = jwt.sign(payload, process.env.JWT_KEY!);
const session = { jwt: token };
const sessionJSON = JSON.stringify(session);
const base64 = Buffer.from(sessionJSON).toString("base64");
return [`session=${base64}`];
};
const buildFirm = async () => {
const firm = Firm.build({
domain: ['@test.com'],
name: 'Test Firm',
type: 'Private Equity'
});
await firm.save();
return firm;
}
global.signin = () => {
const firm = buildFirm();
const payload = {
id: new mongoose.Types.ObjectId().toHexString(),
email: "[email protected]",
name: 'Test Name',
firmId: firm.id, // ERROR OCCURS HERE
root: false,
};
const token = jwt.sign(payload, process.env.JWT_KEY!);
const session = { jwt: token };
const sessionJSON = JSON.stringify(session);
const base64 = Buffer.from(sessionJSON).toString("base64");
return [`session=${base64}`];
};
firm.ts (Firm Model):
import mongoose from 'mongoose';
import { FirmStatus } from "@mavata/common";
export { FirmStatus };
// 1. Interface #1 WHAT IT TAKES TO CREATE A Firm
// An interface that describes the properties
// that are requried to create a new Firm
interface FirmAttrs {
id?: string;
domain: string[];
name: string;
type?: string;
}
// Interface #2 WHAT THE ENTIRE COLLECTION OF FIRMS LOOKS LIKE (METHODS ASSOCIATED WITH THE Firm MODEL)
// An interface that describes the properties
// that a Firm Model has
interface FirmModel extends mongoose.Model<FirmDoc> {
build(attrs: FirmAttrs): FirmDoc;
deleteById(id: string): FirmDoc;
}
// Interface #3 WHAT PROPERTIES A SINGLE Firm HAS
// An interface that describes the properties
// that a Firm Document has
export interface FirmDoc extends mongoose.Document {
domain: string[];
status: FirmStatus;
name: string;
type: string;
users: string[];
companies: string[];
}
const firmSchema = new mongoose.Schema({
domain: [{
type: String,
required: true
}],
name: {
type: String,
required: true
},
status: {
type: String,
enum: Object.values(FirmStatus),
default: FirmStatus.Created,
required: true,
},
type: {
type: String,
default: '',
required: false
},
users: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
default: [],
required: true
}],
companies: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Company',
default: [],
required: true
}]
},
{
// UNCOMMON to be transformation logic inside of a model
// this is defining how some data inside of our model should be VIEWED and transmitted along the network
toJSON: {
transform(doc, ret) {
ret.id = ret._id; // rename _id property to id
delete ret._id; // delete the _id property off the object
delete ret.__v; // delete useless property
}
}
});
// middleware function
// if we used arrow function rather than function declaration as seen below, then
// references to 'this' would refer to THIS Firms.ts file rather than
// refer to the 'Firm' trying to be persisted in the database
firmSchema.pre('save', async function(done) {
// when a Firm instance is being updated, we only want to check if the password has changed
// so only attempt to hash the password if it has been modified
console.log('firm saved of type ', this.type);
done();
});
firmSchema.statics.build = (attrs: FirmAttrs) => {
return new Firm(attrs);
};
firmSchema.statics.deleteById = async (id: string) => {
return await Firm.deleteOne({ _id: new mongoose.Types.ObjectId(id)});
};
const Firm = mongoose.model<FirmDoc, FirmModel>('Firm', firmSchema);
export { Firm };
Where am I going wrong?
CodePudding user response:
Here you are declaring that signin will return an array of strings
declare global {
var signin: () => string[];
}
Since buildFirm
is asynchronous, you are correct in the need to await
it before you can use it in firm.id
. To await
it, you must make signin
an async
function. This also means the function now returns a Promise
.
declare global {
var signin: () => Promise<string[]>
}
You most likely don't need the declare global
blocks at all though.