From bdf03b2a9e26443686c55a5c74ce886f421a5e9b Mon Sep 17 00:00:00 2001 From: Alex <52292902+alexrudd2@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:56:12 -0500 Subject: [PATCH] Add basic server smoke test (#91) --- package-lock.json | 132 +++++++++++++++++++++++++++++ package.json | 3 + src/__tests__/server-smoke.test.ts | 31 +++++++ src/server.ts | 11 +-- 4 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 src/__tests__/server-smoke.test.ts diff --git a/package-lock.json b/package-lock.json index e6f3a1b9..ac805a53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,12 +26,14 @@ "devDependencies": { "@craftamap/esbuild-plugin-html": "^0.6.1", "@rehooks/component-size": "^1.0.2", + "@serialport/bindings-cpp": "^12.0.0", "@types/cors": "^2.8.4", "@types/express": "^4.16.1", "@types/jest": "^29.0.0", "@types/node": "^14.0.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", + "@types/supertest": "^2.0.12", "@types/w3c-web-serial": "^1.0.2", "@types/ws": "8.0.0 - 8.5", "@types/yargs": "^17.0.0", @@ -49,6 +51,7 @@ "react-dom": "^18.0.0", "rimraf": "^5.0.0", "semver": "^7.5.2", + "supertest": "^6.3.3", "ts-jest": "^29.0.0", "typescript": "~5.0 || ~5.2.0" }, @@ -1704,6 +1707,12 @@ "@types/node": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, "node_modules/@types/cors": { "version": "2.8.4", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.4.tgz", @@ -1861,6 +1870,25 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/superagent": { + "version": "4.1.19", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.19.tgz", + "integrity": "sha512-McM1mlc7PBZpCaw0fw/36uFqo0YeA6m8JqoyE4OfqXsZCIg0hPP2xdE6FM7r6fdprDZHlJwDpydUj1R++93hCA==", + "dev": true, + "dependencies": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "node_modules/@types/supertest": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.14.tgz", + "integrity": "sha512-Q900DeeHNFF3ZYYepf/EyJfZDA2JrnWLaSQ0YNV7+2GTo8IlJzauEnDGhya+hauncpBYTYGpVHwGdssJeAQ7eA==", + "dev": true, + "dependencies": { + "@types/superagent": "*" + } + }, "node_modules/@types/w3c-web-serial": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/w3c-web-serial/-/w3c-web-serial-1.0.2.tgz", @@ -2317,6 +2345,12 @@ "node": ">=0.10.0" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, "node_modules/async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", @@ -2882,6 +2916,12 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2948,6 +2988,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -3151,6 +3197,16 @@ "node": ">=8" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/dfa": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", @@ -3949,6 +4005,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -4179,6 +4241,21 @@ "node": ">= 6" } }, + "node_modules/formidable": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "dev": true, + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4419,6 +4496,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/hsluv": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/hsluv/-/hsluv-0.0.3.tgz", @@ -7279,6 +7365,52 @@ "node": ">=0.10.0" } }, + "node_modules/superagent": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", + "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.2", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=6.4.0 <13 || >=14" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/supertest": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", + "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^8.0.5" + }, + "engines": { + "node": ">=6.4.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index e0505de8..92b0f403 100644 --- a/package.json +++ b/package.json @@ -37,12 +37,14 @@ "devDependencies": { "@craftamap/esbuild-plugin-html": "^0.6.1", "@rehooks/component-size": "^1.0.2", + "@serialport/bindings-cpp": "^12.0.0", "@types/cors": "^2.8.4", "@types/express": "^4.16.1", "@types/jest": "^29.0.0", "@types/node": "^14.0.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", + "@types/supertest": "^2.0.12", "@types/w3c-web-serial": "^1.0.2", "@types/ws": "8.0.0 - 8.5", "@types/yargs": "^17.0.0", @@ -60,6 +62,7 @@ "react-dom": "^18.0.0", "rimraf": "^5.0.0", "semver": "^7.5.2", + "supertest": "^6.3.3", "ts-jest": "^29.0.0", "typescript": "~5.0 || ~5.2.0" }, diff --git a/src/__tests__/server-smoke.test.ts b/src/__tests__/server-smoke.test.ts new file mode 100644 index 00000000..c39ca236 --- /dev/null +++ b/src/__tests__/server-smoke.test.ts @@ -0,0 +1,31 @@ +import { startServer, waitForEbb } from '../server'; +import type { Server } from 'http'; +import request from 'supertest'; + +jest.mock("../serialport-serialport") +jest.mock("../ebb") +jest.mock("../server", () => { + const original = jest.requireActual("../server"); + return { + ...original, + waitForEbb: jest.fn(()=> 'fake-ebb-path'), + }; +}); + +describe('Server Smoke Test', () => { +let server: Server; + + beforeAll(async () => { + server = await startServer(0); + }); + + afterAll( () => { + server.close(); + }); + + test('POST /cancel should return 200 OK', async () => { + const response = await request(server).post('/cancel'); + expect(response.status).toBe(200); + }); + +}); diff --git a/src/server.ts b/src/server.ts index 21e3c07c..0d8029bb 100644 --- a/src/server.ts +++ b/src/server.ts @@ -11,8 +11,9 @@ import { EBB } from "./ebb"; import { Device, PenMotion, Motion, Plan } from "./planning"; import { formatDuration } from "./util"; import { autoDetect } from '@serialport/bindings-cpp'; +import * as self from './server' // for mocking -export function startServer(port: number, device: string | null = null, enableCors = false, maxPayloadSize = "200mb") { +export function startServer(port: number, device: string | null = null, enableCors = false, maxPayloadSize = "200mb"): Promise { const app = express(); app.use("/", express.static(path.join(__dirname, "..", "ui"))); @@ -219,7 +220,7 @@ export function startServer(port: number, device: string | null = null, enableCo await plotter.postPlot(); } - return new Promise((resolve) => { + return new Promise((resolve) => { server.listen(port, () => { async function connect() { for await (const d of ebbs(device)) { @@ -256,8 +257,8 @@ async function listEBBs() { return ports.filter(isEBB).map((p: { path: any; }) => p.path); } -async function waitForEbb() { -// eslint-disable-next-line no-constant-condition +export async function waitForEbb() { + // eslint-disable-next-line no-constant-condition while (true) { const ebbs = await listEBBs(); if (ebbs.length) { @@ -270,7 +271,7 @@ async function waitForEbb() { async function* ebbs(path?: string) { while (true) { try { - const com = path || (await waitForEbb()); + const com = path || (await self.waitForEbb()); // use self-import for mocking console.log(`Found EBB at ${com}`); const port = await tryOpen(com); const closed = new Promise((resolve) => {