From 6c2de9fbad33e284d7fa6b35770b7093d6a5cd7e Mon Sep 17 00:00:00 2001 From: hatskier Date: Sun, 17 Jul 2022 21:55:38 +0200 Subject: [PATCH] feat: route for historical packages fetching implemented --- routes/packages.ts | 155 +++++++++++++++++++++-------------- test/routes/packages.test.ts | 63 ++++++++++++-- 2 files changed, 149 insertions(+), 69 deletions(-) diff --git a/routes/packages.ts b/routes/packages.ts index 6e1b103..d633304 100644 --- a/routes/packages.ts +++ b/routes/packages.ts @@ -10,79 +10,110 @@ import { Document } from "mongoose"; const dbItemToObj = (item: Document & Package) => { return _.omit(item.toObject(), ["_id", "__v"]); -} +}; + +const findPackage = async (req, res, initialMongoQuery) => { + const provider = await getProviderFromParams( + req.query as { provider: string } + ); + + if (!provider.address) { + throw new Error("Provider address is required"); + } + + const symbol = req.query.symbol as string; + + if (symbol) { + // Fetching latest price for symbol from DB + const price = await Price.findOne({ + ...initialMongoQuery, + provider: provider.address, + symbol, + }).sort({ timestamp: -1 }); + + if (!price) { + throw new Error(`Requested package not found for symbol: ${symbol}`); + } + + const responseObj = { + ..._.pick(price, ["timestamp", "provider"]), + signature: price.evmSignature?.toString("base64"), + liteSignature: price.liteEvmSignature.toString("base64"), + prices: [{ symbol, value: price.value }], + signer: provider.evmAddress, // TODO: we don't really need signer, as it must be fetched from a trusted source or hardcoded in the redstone-evm-connector + }; + + return res.json(responseObj); + } else { + // Fetching latest package from DB + const packageFromDB = await Package.findOne({ + ...initialMongoQuery, + provider: provider.address, + }).sort({ timestamp: -1 }); + + if (!packageFromDB) { + throw new Error(`Requested package not found`); + } + + const responseObj = dbItemToObj(packageFromDB); + return res.json(responseObj); + } +}; export const packages = (router: Router) => { /** * This endpoint is used for publishing a new price package - */ - router.post("/packages", asyncHandler(async (req, res) => { - // Saving package in DB - const newPackage = new Package(req.body); - await newPackage.save(); + */ + router.post( + "/packages", + asyncHandler(async (req, res) => { + // Saving package in DB + const newPackage = new Package(req.body); + await newPackage.save(); - // Cleaning older packages of the same provider before in the lite mode - if (enableLiteMode) { - await tryCleanCollection(Package, { - signer: req.body.signer, - timestamp: { $lt: newPackage.timestamp - cacheTTLMilliseconds }, + // Cleaning older packages of the same provider before in the lite mode + if (enableLiteMode) { + await tryCleanCollection(Package, { + signer: req.body.signer, + timestamp: { $lt: newPackage.timestamp - cacheTTLMilliseconds }, + }); + } + + // Returning package id in response + return res.json({ + msg: "Package saved", + id: newPackage._id, }); - } - - // Returning package id in response - return res.json({ - msg: "Package saved", - id: newPackage._id, - }); - })); + }) + ); /** * This endpoint is used for fetching the latest * packages for the specified provider - */ - router.get("/packages/latest", asyncHandler(async (req, res) => { - const provider = await getProviderFromParams(req.query as { provider: string; }); + */ + router.get( + "/packages/latest", + asyncHandler(async (req, res) => { + const initialMongoQuery = {}; + return await findPackage(req, res, initialMongoQuery); + }) + ); - if (!provider.address) { - throw new Error("Provider address is required"); - } - - const symbol = req.query.symbol as string; - - if (symbol) { - // Fetching latest price for symbol from DB - const price = await Price.findOne({ - provider: provider.address, - symbol, - }) - .sort({ timestamp: -1 }); - - if (!price) { - throw new Error(`Value not found for symbol: ${symbol}`); + /** + * This endpoint is used for fetching historical + * packages for the specified provider and timestamp + */ + router.get( + "/packages", + asyncHandler(async (req, res) => { + if (!req.query.toTimestamp) { + throw new Error("toTimestamp query param is required"); } - - const responseObj = { - ..._.pick(price, ["timestamp", "provider"]), - signature: price.evmSignature?.toString("base64"), - liteSignature: price.liteEvmSignature.toString("base64"), - prices: [{ symbol, value: price.value }], - signer: provider.evmAddress, // TODO: we don't really need signer, as it must be fetched from a trusted source or hardcoded in the redstone-evm-connector + + const initialMongoQuery = { + timestamp: { $lte: req.query.toTimestamp }, }; - - return res.json(responseObj); - } else { - // Fetching latest package from DB - const packageFromDB = await Package.findOne({ - provider: provider.address, - }) - .sort({ timestamp: -1 }); - - if (!packageFromDB) { - throw new Error(`Latest package not found`); - } - - const responseObj = dbItemToObj(packageFromDB); - return res.json(responseObj); - } - })); + return await findPackage(req, res, initialMongoQuery); + }) + ); }; diff --git a/test/routes/packages.test.ts b/test/routes/packages.test.ts index b9b3f7f..1bb85e9 100644 --- a/test/routes/packages.test.ts +++ b/test/routes/packages.test.ts @@ -33,15 +33,12 @@ describe("Testing packages route", () => { evmSignature: Buffer.from(evmSignature, "base64"), liteEvmSignature: Buffer.from(liteEvmSignature, "base64"), version: "0.4", - source: {"test": 123}, + source: { test: 123 }, timestamp: testTimestamp, }; test("Should post a package and fetch it", async () => { - await request(app) - .post("/packages") - .send(testPackage) - .expect(200); + await request(app).post("/packages").send(testPackage).expect(200); const response = await request(app) .get(`/packages/latest?provider=${provider.name}`) @@ -58,19 +55,71 @@ describe("Testing packages route", () => { await new Price({ ...testPrice, }).save(); - // await new Package(testPackage).save(); + + // When + const response = await request(app).get("/packages/latest").query({ + provider: provider.name, + symbol: testPrice.symbol, + }); + + // Then + expect(response.body).toEqual({ + ...testPackage, + signer: "0x926E370fD53c23f8B71ad2B3217b227E41A92b12", + signature: evmSignature, + liteSignature: liteEvmSignature, + prices: [_.pick(testPrice, ["value", "symbol"])], + }); + }); + + test("Should get a historical package", async () => { + const olderPackage = { + ...testPackage, + timestamp: testPackage.timestamp - 50 * 1000, + }; + await request(app).post("/packages").send(testPackage).expect(200); + await request(app).post("/packages").send(olderPackage).expect(200); + + const response = await request(app) + .get( + `/packages/?provider=${provider.name}&toTimestamp=${ + olderPackage.timestamp + 1 + }` + ) + .expect(200); + + expect(response.body).toEqual({ + ...olderPackage, + prices: [], + }); + }); + + test("Should get a historical package by symbol", async () => { + // Given + const olderPrice = { + ...testPrice, + timestamp: testPrice.timestamp - 50 * 1000, + }; + await new Price({ + ...testPrice, + }).save(); + await new Price({ + ...olderPrice, + }).save(); // When const response = await request(app) - .get("/packages/latest") + .get("/packages") .query({ provider: provider.name, symbol: testPrice.symbol, + toTimestamp: olderPrice.timestamp + 1, }); // Then expect(response.body).toEqual({ ...testPackage, + timestamp: olderPrice.timestamp, signer: "0x926E370fD53c23f8B71ad2B3217b227E41A92b12", signature: evmSignature, liteSignature: liteEvmSignature,