Home > Blockchain >  "Property 'id' does not exist on type 'Promise<FirmDoc>'" // &qu
"Property 'id' does not exist on type 'Promise<FirmDoc>'" // &qu

Time:10-26

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.

  • Related