diff --git a/src/app.ts b/src/app.ts index 3378e35..46bad3b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,20 +2,13 @@ import cors from "cors"; import express from "express"; import httpContext from "express-http-context"; import morgan from "morgan"; -import { homedir } from "os"; -import path from "path"; import { logger } from "./logger"; -import { setDir } from "./util"; -import { APP_DIR } from "./vars"; let reqId = 0; // Create Express server const app = express(); -// Create app directory if it doesn't exist -setDir(path.join(homedir(), APP_DIR)); - // Allow request from any origin app.use(cors()); diff --git a/src/controllers/api.ts b/src/controllers/api.ts index 982f512..6ddbcea 100644 --- a/src/controllers/api.ts +++ b/src/controllers/api.ts @@ -30,23 +30,13 @@ import { } from "../errors"; import { logger } from "../logger"; import { genHashFromFile, setDir } from "../util"; -import { APP_DIR } from "../vars"; +import { DEFAULT_APP_DIR_NAME } from "../vars"; import { APP_VERSION } from "../version"; const PROTOCOL_VERSION = "0.2"; const DATABASE_FILE = "app.db"; -const appDir = path.join(homedir(), APP_DIR); -const tempDir = path.join(appDir, "tmp"); -const consignmentDir = path.join(appDir, "consignments"); -const mediaDir = path.join(appDir, "media"); - -// We make sure the directories exist -setDir(tempDir); -setDir(consignmentDir); -setDir(mediaDir); - const storage = multer.diskStorage({ destination: function (_req, _file, cb) { cb(null, tempDir); @@ -55,6 +45,12 @@ const storage = multer.diskStorage({ const upload = multer({ storage }); +let appDir: string; +let ds: Datastore<{ _id: string }>; +let tempDir: string; +let consignmentDir: string; +let mediaDir: string; + interface ServerInfo { version: string; protocol_version: string; @@ -83,8 +79,6 @@ interface Media { attachment_id: string; } -const ds = Datastore.create(path.join(homedir(), APP_DIR, DATABASE_FILE)); - function isBoolean(data: unknown): data is boolean { return Boolean(data) === data; } @@ -375,6 +369,18 @@ jsonRpcServer.addMethod( ); export const loadApiEndpoints = (app: Application): void => { + // setup app directories + appDir = process.env.APP_DIR || path.join(homedir(), DEFAULT_APP_DIR_NAME); + setDir(appDir); + ds = Datastore.create(path.join(appDir, DATABASE_FILE)); + tempDir = path.join(appDir, "tmp"); + consignmentDir = path.join(appDir, "consignments"); + mediaDir = path.join(appDir, "media"); + setDir(tempDir); + setDir(consignmentDir); + setDir(mediaDir); + + // setup app route app.post( "/json-rpc", upload.single("file"), diff --git a/src/vars.ts b/src/vars.ts index 3ba547c..a874ec7 100644 --- a/src/vars.ts +++ b/src/vars.ts @@ -1 +1 @@ -export const APP_DIR = process.env.APP_DIR || ".rgb-proxy-server"; +export const DEFAULT_APP_DIR_NAME = ".rgb-proxy-server"; diff --git a/test/api.spec.ts b/test/api.spec.ts index d99322b..254cc4c 100644 --- a/test/api.spec.ts +++ b/test/api.spec.ts @@ -1,24 +1,220 @@ +import fs from "fs"; +import path from "path"; import request from "supertest"; import app from "../src/app"; import { loadApiEndpoints } from "../src/controllers/api"; +const jsonrpcVersion = "2.0"; +const okStatus = 200; +const contentTypeForm = "multipart/form-data"; + +// temporary test directory +const tempDir = fs.mkdtempSync(path.join(__dirname, "temp-")); +process.env.APP_DIR = path.join(tempDir, "app-dir"); + loadApiEndpoints(app); +afterAll(() => { + // cleanup the temporary directory + return fs.promises.rm(tempDir, { recursive: true, force: true }); +}); + describe("POST /json-rpc", () => { + it("ack.post should succeed on 1st try then return false", async () => { + const consignmentPath = path.join(tempDir, "ack.post"); + fs.writeFileSync(consignmentPath, "consignment ack binary data"); + let reqID = "1"; + const recipientID = "ackTest.post"; + const txid = "aTxid"; + let res = await request(app) + .post("/json-rpc") + .set("Content-type", contentTypeForm) + .field("jsonrpc", jsonrpcVersion) + .field("id", reqID) + .field("method", "consignment.post") + .field("params[recipient_id]", recipientID) + .field("params[txid]", txid) + .attach("file", fs.createReadStream(consignmentPath)) + .expect(okStatus); + expect(res.body.result).toStrictEqual(true); + + reqID = "2"; + const method = "ack.post"; + let req = { + jsonrpc: jsonrpcVersion, + id: reqID, + method: method, + params: { + recipient_id: recipientID, + ack: true, + }, + }; + res = await request(app).post("/json-rpc").send(req).expect(okStatus); + expect(res.body.jsonrpc).toStrictEqual(jsonrpcVersion); + expect(res.body.id).toStrictEqual(reqID); + expect(res.body.result).toStrictEqual(true); + + reqID = "3"; + req = { + jsonrpc: jsonrpcVersion, + id: reqID, + method: method, + params: { + recipient_id: recipientID, + ack: true, + }, + }; + res = await request(app).post("/json-rpc").send(req).expect(okStatus); + expect(res.body.id).toStrictEqual(reqID); + expect(res.body.result).toStrictEqual(false); + }); + + it("consignment.get should succeed", async () => { + const consignmentPath = path.join(tempDir, "consignment.get"); + fs.writeFileSync(consignmentPath, "consignment get binary data"); + const consignment = fs.readFileSync(consignmentPath); + const consignmentBase64 = consignment.toString("base64"); + let reqID = "1"; + const recipientID = "blindTest.get"; + const txid = "aTxid"; + let res = await request(app) + .post("/json-rpc") + .set("Content-type", contentTypeForm) + .field("jsonrpc", jsonrpcVersion) + .field("id", reqID) + .field("method", "consignment.post") + .field("params[recipient_id]", recipientID) + .field("params[txid]", txid) + .attach("file", fs.createReadStream(consignmentPath)) + .expect(okStatus); + expect(res.body.result).toStrictEqual(true); + + reqID = "2"; + const req = { + jsonrpc: jsonrpcVersion, + id: reqID, + method: "consignment.get", + params: { + recipient_id: recipientID, + }, + }; + res = await request(app).post("/json-rpc").send(req).expect(okStatus); + expect(res.body.jsonrpc).toStrictEqual(jsonrpcVersion); + expect(res.body.id).toStrictEqual(reqID); + expect(res.body.result.consignment).toStrictEqual(consignmentBase64); + expect(res.body.result.txid).toStrictEqual(txid); + }); + + it("consignment.post should succeed on 1st try then return false", async () => { + const consignmentPath = path.join(tempDir, "consignment.post"); + fs.writeFileSync(consignmentPath, "consignment post binary data"); + let reqID = "1"; + const method = "consignment.post"; + const recipientID = "blindTest.post"; + const txid = "aTxid"; + let res = await request(app) + .post("/json-rpc") + .set("Content-type", contentTypeForm) + .field("jsonrpc", jsonrpcVersion) + .field("id", reqID) + .field("method", method) + .field("params[recipient_id]", recipientID) + .field("params[txid]", txid) + .attach("file", fs.createReadStream(consignmentPath)) + .expect(okStatus); + expect(res.body.jsonrpc).toStrictEqual(jsonrpcVersion); + expect(res.body.id).toStrictEqual(reqID); + expect(res.body.result).toStrictEqual(true); + + reqID = "2"; + res = await request(app) + .post("/json-rpc") + .set("Content-type", contentTypeForm) + .field("jsonrpc", jsonrpcVersion) + .field("id", reqID) + .field("method", method) + .field("params[recipient_id]", recipientID) + .field("params[txid]", txid) + .attach("file", fs.createReadStream(consignmentPath)) + .expect(okStatus); + expect(res.body.id).toStrictEqual(reqID); + expect(res.body.result).toStrictEqual(false); + }); + + it("media.get should succeed", async () => { + const mediaPath = path.join(tempDir, "media.post"); + fs.writeFileSync(mediaPath, "media get binary data"); + const media = fs.readFileSync(mediaPath); + const mediaBase64 = media.toString("base64"); + let reqID = "1"; + const attachmentID = "mediaTest.get"; + let res = await request(app) + .post("/json-rpc") + .set("Content-type", contentTypeForm) + .field("jsonrpc", jsonrpcVersion) + .field("id", reqID) + .field("method", "media.post") + .field("params[attachment_id]", attachmentID) + .attach("file", fs.createReadStream(mediaPath)) + .expect(okStatus); + expect(res.body.result).toStrictEqual(true); + + reqID = "2"; + const req = { + jsonrpc: jsonrpcVersion, + id: reqID, + method: "media.get", + params: { + attachment_id: attachmentID, + }, + }; + res = await request(app).post("/json-rpc").send(req).expect(okStatus); + expect(res.body.jsonrpc).toStrictEqual(jsonrpcVersion); + expect(res.body.id).toStrictEqual(reqID); + expect(res.body.result).toStrictEqual(mediaBase64); + }); + + it("media.post should succeed on 1st try then return false", async () => { + const mediaPath = path.join(tempDir, "media.post"); + fs.writeFileSync(mediaPath, "media post binary data"); + let reqID = "1"; + const method = "media.post"; + const attachmentID = "mediaTest.post"; + let res = await request(app) + .post("/json-rpc") + .set("Content-type", contentTypeForm) + .field("jsonrpc", jsonrpcVersion) + .field("id", reqID) + .field("method", method) + .field("params[attachment_id]", attachmentID) + .attach("file", fs.createReadStream(mediaPath)) + .expect(okStatus); + expect(res.body.jsonrpc).toStrictEqual(jsonrpcVersion); + expect(res.body.id).toStrictEqual(reqID); + expect(res.body.result).toStrictEqual(true); + reqID = "2"; + res = await request(app) + .post("/json-rpc") + .set("Content-type", contentTypeForm) + .field("jsonrpc", jsonrpcVersion) + .field("id", reqID) + .field("method", method) + .field("params[attachment_id]", attachmentID) + .attach("file", fs.createReadStream(mediaPath)) + .expect(okStatus); + expect(res.body.id).toStrictEqual(reqID); + expect(res.body.result).toStrictEqual(false); + }); + it("server.info should succeed", async () => { - const jsonrpcVersion = "2.0"; const reqID = 1; const req = { jsonrpc: jsonrpcVersion, id: reqID, method: "server.info", }; - const res = await request(app) - .post("/json-rpc") - .set("Content-type", "application/json") - .send(req) - .expect(200); + const res = await request(app).post("/json-rpc").send(req).expect(okStatus); expect(res.body.jsonrpc).toStrictEqual(jsonrpcVersion); expect(res.body.id).toStrictEqual(reqID); expect(res.body.result.protocol_version).toStrictEqual("0.2");