Skip to content

Commit

Permalink
Getting the following error because I used ObjectId from bson
Browse files Browse the repository at this point in the history
  ● Test suite failed to run

    src/impl/mongoose/dao/MongooseDaoFactory.ts:45:5 - error TS2322: Type 'OfferDao' is not assignable to type 'IOfferDao<ObjectId, IOffer<ObjectId>>'.
      Types of property 'loadSubOffers' are incompatible.
        Type '(parentOfferId: ObjectId) => Promise<IMongooseOffer[]>' is not assignable to type '(parentOfferId: ObjectId) => Promise<IOffer<ObjectId>[]>'.
          Types of parameters 'parentOfferId' and 'parentOfferId' are incompatible.
            Type 'ObjectId' is missing the following properties from type 'ObjectId': _bsontype, id, toHexString, toJSON, and 3 more.

    45     return this.offerDao;

 Known issue:
 Automattic/mongoose#12711
 Automattic/mongoose#12537
  • Loading branch information
zhamdi committed Oct 5, 2023
1 parent 1cc83f9 commit d5b4f4e
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 17 deletions.
5 changes: 4 additions & 1 deletion README.md
Expand Up @@ -29,8 +29,11 @@ UserCredits is built with a modular architecture that separates core business lo
To start using UserCredits in your project, follow the installation and usage instructions in the [blog series](https://dev.to/zhamdi/architecting-pay-as-you-go-magic-usercredits-winning-formula-4ace).

## Testing
We are using the project mongodb-memory-server to run an in memory mongodb for tests. Which generates the following warning

`(node:36336) ExperimentalWarning: VM Modules is an experimental feature and might change at any time`
### Prerequisites
Jest must be launched with the node option `--experimental-vm-modules`to enable ecma6 syntax in config files
Jest must be launched with the node option `--experimental-vm-modules` also to enable ecma6 syntax in config files

## Contributing

Expand Down
51 changes: 50 additions & 1 deletion config/jest/setup.js
@@ -1,22 +1,71 @@
/* global beforeAll, console, afterAll, expect */
// setup.js
import { asValue } from "awilix";
import { MongoMemoryServer } from "mongodb-memory-server";

import { MongooseDaoFactory } from "../../src/impl/mongoose/dao/MongooseDaoFactory";
import { testContainer } from "../../test/testContainer";

let mongoServer;

beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
const uri = mongoServer.getUri();
console.log("URI for mongodb: ", uri);
testContainer.register({
mongoUri: asValue(uri), // Register it as a value dependency
});
const mongooseDaoFactory = new MongooseDaoFactory(uri, "userCredits");
testContainer.register({ mongooseDaoFactory: asValue(mongooseDaoFactory) });

// Use `uri` and `dbName` in your Awilix container setup.
// Register them as constants or inject them as needed.
});

afterAll(async () => {
await mongoServer.stop();
// FIXME uncomment the following line: it's commented to allow us to connect and check the db for now
// await mongoServer.stop();
});

// Define a Jest utility function to compare objects field by field
expect.extend({
toHaveSameFields(received, expected) {
const receivedKeys = Object.keys(received);
const expectedKeys = Object.keys(expected);

// Check if the number of fields is the same
if (receivedKeys.length !== expectedKeys.length) {
return {
message: () => `Expected objects to have the same number of fields.`,
pass: false,
};
}

const differences = [];

// Compare fields and their values
for (const key of receivedKeys) {
const receivedValue = received[key];
const expectedValue = expected[key];

if (receivedValue !== expectedValue) {
differences.push(key);
}
}

// Determine if the objects have the same fields
const pass = differences.length === 0;

const message = pass
? () => `Expected objects not to have the same fields.`
: () =>
`Expected objects to have the same fields, but found differences in: ${differences.join(
", ",
)}.`;

return {
message,
pass,
};
},
});
2 changes: 1 addition & 1 deletion src/db/dao/IBaseDao.ts
Expand Up @@ -13,7 +13,7 @@ export interface IBaseDao<D extends object> {

findById(userId: object): Promise<D | null>;

findOne(query: object): Promise<D>;
// findOne(query: object): Promise<D>;

// Update a document by ID
updateById(userId: string, update: Partial<D>): Promise<D | null>;
Expand Down
2 changes: 2 additions & 0 deletions src/impl/mongoose/dao/OfferDao.ts
Expand Up @@ -17,4 +17,6 @@ export class OfferDao
// Use find() to get sub-offers based on the parentOfferId
return this.find({ parentOfferId });
}


}
5 changes: 3 additions & 2 deletions src/impl/mongoose/model/Offer.ts
@@ -1,4 +1,5 @@
import mongoose, { Document, Model, ObjectId, Schema } from "mongoose";
import { ObjectId } from "bson";
import mongoose, { Document, Model, Schema } from "mongoose";

import { IOffer } from "../../../db/model";

Expand All @@ -17,7 +18,7 @@ const offerSchema = new Schema<IMongooseOffer>({
parentOfferId: {
ref: "IOffer",
required: true,
type: mongoose.Schema.Types.ObjectId,
type: Schema.Types.ObjectId,
},
price: { required: true, type: Number },
tokenCount: { required: true, type: Number },
Expand Down
7 changes: 4 additions & 3 deletions src/impl/mongoose/model/Order.ts
@@ -1,4 +1,5 @@
import mongoose, { Document, Model, ObjectId, Schema } from "mongoose";
import { ObjectId } from "bson";
import mongoose, { Document, Model, Schema } from "mongoose";

import { IOrder, OrderStatus } from "../../../db/model/IOrder";

Expand All @@ -20,7 +21,7 @@ const orderSchema = new Schema<IMongooseOrder>(
offerId: {
ref: "IOffer",
required: true,
type: mongoose.Schema.Types.ObjectId,
type: Schema.Types.ObjectId,
},
status: {
enum: ["pending", "paid", "refused"],
Expand All @@ -31,7 +32,7 @@ const orderSchema = new Schema<IMongooseOrder>(
userId: {
ref: "User",
required: true,
type: mongoose.Schema.Types.ObjectId,
type: Schema.Types.ObjectId,
},
},
{ timestamps: true },
Expand Down
5 changes: 3 additions & 2 deletions src/impl/mongoose/model/TokenTimetable.ts
@@ -1,4 +1,5 @@
import mongoose, { Document, ObjectId, Schema } from "mongoose";
import { ObjectId } from "bson";
import mongoose, { Document, Schema } from "mongoose";

import { ITokenTimetable } from "../../../db/model/ITokenTimetable";

Expand All @@ -10,7 +11,7 @@ const tokenTimetableSchema = new Schema<ITokenTimetable<ObjectId>>(
userId: {
ref: "User",
required: true,
type: mongoose.Schema.Types.ObjectId,
type: Schema.Types.ObjectId,
},
},
{ timestamps: { createdAt: true, updatedAt: false } },
Expand Down
7 changes: 4 additions & 3 deletions src/impl/mongoose/model/UserCredits.ts
@@ -1,4 +1,5 @@
import mongoose, { Document, Model, ObjectId, Schema} from "mongoose";
import { ObjectId } from "bson";
import mongoose, { Document, Model, Schema} from "mongoose";

import { ISubscription, IUserCredits } from "../../../db/model/IUserCredits";

Expand All @@ -12,7 +13,7 @@ const subscriptionSchema = new Schema<
offerId: {
ref: "IOffer",
required: true,
type: mongoose.Schema.Types.ObjectId,
type: Schema.Types.ObjectId,
},
starts: Date,
status: {
Expand All @@ -29,7 +30,7 @@ const userCreditsSchema = new Schema<IMongooseUserCredits>(
userId: {
ref: "User",
required: true,
type: mongoose.Schema.Types.ObjectId,
type: Schema.Types.ObjectId,
},
},
{ timestamps: true },
Expand Down
6 changes: 6 additions & 0 deletions src/service/BaseService.ts
Expand Up @@ -34,6 +34,10 @@ export class BaseService<K extends object> implements IPayment<K> {
this.userCreditsDao = daoFactory.getUserCreditsDao();
}

getDaoFactory(): IDaoFactory<K> {
return this.daoFactory;
}

/**
* Load offers based on user ID, applying overriding logic for subOffers.
* @param userId The user's ID.
Expand Down Expand Up @@ -139,4 +143,6 @@ export class BaseService<K extends object> implements IPayment<K> {
{} as Record<string, IOffer<K>[]>,
);
}


}
2 changes: 2 additions & 0 deletions src/service/IPayment.ts
@@ -1,11 +1,13 @@
import { IOffer } from "../db/model/IOffer";
import { IOrder } from "../db/model/IOrder";
import { IUserCredits } from "../db/model/IUserCredits";
import { IDaoFactory } from "../db/dao";

/**
* @param K the type of foreign keys (is used for all foreign keys type)
*/
export interface IPayment<K extends object> {
getDaoFactory(): IDaoFactory<K>;
// createOrder(offerId: unknown, userId: unknown): Promise<IOrder<K>>;
// execute(order: IOrder<K>): Promise<IUserCredits<K>>;
loadOffers(userId: unknown): Promise<IOffer<K>[]>;
Expand Down
35 changes: 32 additions & 3 deletions test/service/BaseService.test.ts
Expand Up @@ -88,7 +88,9 @@ const offerChild2: IOffer<ObjectId> = {
} as IOffer<ObjectId>;

const daoFactoryMock: IDaoFactory<ObjectId> =
testContainer.resolve("daoFactory");
testContainer.resolve("daoFactoryMock");
const mongooseDaoFactory: IDaoFactory<ObjectId> =
testContainer.resolve("mongooseDaoFactory");

describe("BaseService.getActiveSubscriptions", () => {
const sampleUserCredits: IUserCredits<ObjectId> = {
Expand Down Expand Up @@ -145,14 +147,16 @@ describe("BaseService.getActiveSubscriptions", () => {
await service.getActiveSubscriptions(sampleUserId);

// Assert that userCreditsDao.findById was called with the correct userId
expect(daoFactoryMock.getUserCreditsDao().findById).toHaveBeenCalledWith(sampleUserId);
expect(daoFactoryMock.getUserCreditsDao().findById).toHaveBeenCalledWith(
sampleUserId,
);

// Assert that activeSubscriptions is an empty array
expect(activeSubscriptions).toEqual([]);
});
});

describe("mergeOffers", () => {
describe("mergeOffers tests", () => {
let service: BaseService<ObjectId>;
beforeEach(() => {
// Create a new instance of BaseService with the mock userCreditsDao
Expand All @@ -179,3 +183,28 @@ describe("mergeOffers", () => {
expect(mergedOffers).toEqual([]);
});
});

describe("offer creation", () => {
let service: BaseService<ObjectId>;
beforeEach(() => {
// Create a new instance of BaseService with the mock userCreditsDao
service = new BaseService<ObjectId>(mongooseDaoFactory);
});

it("should create offer then retrieve it", async () => {
const offerDao = service.getDaoFactory().getOfferDao();
const createdOffer = await offerDao.create(offerRoot1);

// Expect that the createdOffer is not null
expect(createdOffer).toBeTruthy();

// Retrieve the offer by its ID
const retrievedOffer = await offerDao.findById(createdOffer._id);

// Expect that the retrievedOffer is not null and has the same properties as the sampleOffer
expect(retrievedOffer).toBeTruthy();
expect(retrievedOffer?._id).toEqual(sampleOffer._id);
expect(retrievedOffer?.cycle).toEqual(sampleOffer.cycle);
expect(retrievedOffer?.kind).toEqual(sampleOffer.kind);
});
});
2 changes: 1 addition & 1 deletion test/testContainer.ts
Expand Up @@ -22,7 +22,7 @@ const sampleUserCredits = {
} as IUserCredits<ObjectId>;

testContainer.register({
daoFactory: asFunction(() => {
daoFactoryMock: asFunction(() => {
const offerDaoMock = new MockOfferDao({} as IOffer<ObjectId>);
const orderDaoMock = new MockOrderDao({} as IOrder<ObjectId>);
const tokenTimetableMock = new MockTokenTimetableDao(
Expand Down

0 comments on commit d5b4f4e

Please sign in to comment.