diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 38346a629..2c19cae2d 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -20,6 +20,28 @@ From the project root, run the following commands - __Install the project dependencies__ by running `pnpm i` - __Run dev mode__ by running `turbo dev` +## TESTING + +Generally we have 2 types of tests. +- Unit tests for functions that contain business logic +- End-to-end tests for core features + +### Unit tests +Unit tests are contained in mostly all the apps and packages (client, server and utils) + +You can run unit tests by running turbo `turbo test:pipeline` from the project root. +This will run all tests and close test runner. + +Alternatively you can navigate to an app or project and run `pnpm test` to run those tests in watch mode + +### E2E tests +E2E tests are in a separate package. On running, [playwright](https://playwright.dev/) will spin up an instance of the webserver to test against +These tests also run against a separate version of the DB (test-db) + +You can run playwright tests from project root with `pnpm e2e` + +When writing tests, it can be handy to run playwright in interactive mode with `pnpm e2e:i`. You would need to manually start the webserver with `pnpm dev:server` + ## CREATE AN INSTALLABLE FILE (Windows | MacOS | Linux) Ontime uses Electron to distribute the application. diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 4e9745c4b..86a639750 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -213,13 +213,13 @@ export const shutdown = async (exitCode = 0) => { process.on('exit', (code) => console.log(`Ontime exited with code: ${code}`)); -process.on('unhandledRejection', async () => { - logger.error('SERVER', 'Error: unhandled rejection'); +process.on('unhandledRejection', async (error) => { + logger.error('SERVER', `Error: unhandled rejection ${error}`); await shutdown(1); }); -process.on('uncaughtException', async () => { - logger.error('SERVER', 'Error: uncaught exception'); +process.on('uncaughtException', async (error) => { + logger.error('SERVER', `Error: uncaught exception ${error}`); await shutdown(1); }); diff --git a/apps/server/src/controllers/ontimeController.js b/apps/server/src/controllers/ontimeController.js index 96612f953..6834d8c7c 100644 --- a/apps/server/src/controllers/ontimeController.js +++ b/apps/server/src/controllers/ontimeController.js @@ -288,13 +288,3 @@ export const dbUpload = async (req, res) => { const file = req.file.path; await uploadAndParse(file, req, res, options); }; - -// Create controller for POST request to '/ontime/dbpath' -// Returns - -export const dbPathToUpload = async (req, res) => { - if (!req.body.path) { - res.status(400).send({ message: 'Path to file not found' }); - return; - } - await uploadAndParse(req.body.path, req, res); -}; diff --git a/apps/server/src/routes/ontimeRouter.js b/apps/server/src/routes/ontimeRouter.js index 105eb9686..3439df3bc 100644 --- a/apps/server/src/routes/ontimeRouter.js +++ b/apps/server/src/routes/ontimeRouter.js @@ -2,7 +2,6 @@ import express from 'express'; import { uploadFile } from '../utils/upload.js'; import { dbDownload, - dbPathToUpload, dbUpload, getAliases, getInfo, @@ -19,11 +18,11 @@ import { } from '../controllers/ontimeController.js'; import { - viewValidator, validateAliases, - validateUserFields, - validateSettings, validateOSC, + validateSettings, + validateUserFields, + viewValidator, } from '../controllers/ontimeController.validate.js'; export const router = express.Router(); @@ -69,6 +68,3 @@ router.get('/osc', getOSC); // create route between controller and '/ontime/osc' endpoint router.post('/osc', validateOSC, postOSC); - -// create route between controller and '/ontime/dbpath' endpoint -router.post('/dbpath', dbPathToUpload); diff --git a/apps/test-db/db.json b/apps/test-db/db.json new file mode 100644 index 000000000..d40f05e04 --- /dev/null +++ b/apps/test-db/db.json @@ -0,0 +1,90 @@ +{ + "rundown": [], + "eventData": { + "title": "All about Carlos demo event", + "publicUrl": "www.getontime.no", + "publicInfo": "WiFi: demoproject \nPassword: ontimeproject", + "backstageUrl": "www.getontime.no", + "backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject", + "endMessage": "" + }, + "settings": { + "app": "ontime", + "version": 2, + "serverPort": 4001, + "lock": null, + "pinCode": "1234", + "timeFormat": "24" + }, + "viewSettings": { + "overrideStyles": false + }, + "aliases": [ + { + "id": "0b0b3", + "enabled": true, + "alias": "test", + "pathAndParams": "lower?bg=ff2&text=f00&size=0.6&transition=5" + } + ], + "userFields": { + "user0": "user0", + "user1": "user1", + "user2": "user2", + "user3": "user3", + "user4": "user4", + "user5": "user5", + "user6": "user6", + "user7": "user7", + "user8": "user8", + "user9": "user9" + }, + "osc": { + "portIn": 8888, + "portOut": 9999, + "targetIP": "127.0.0.1", + "enabledIn": false, + "enabledOut": false, + "subscriptions": { + "onLoad": [], + "onStart": [], + "onPause": [], + "onStop": [], + "onUpdate": [], + "onFinish": [] + } + }, + "http": { + "http": { + "user": null, + "pwd": null, + "messages": { + "onLoad": { + "url": "", + "enabled": false + }, + "onStart": { + "url": "", + "enabled": false + }, + "onUpdate": { + "url": "", + "enabled": false + }, + "onPause": { + "url": "", + "enabled": false + }, + "onStop": { + "url": "", + "enabled": false + }, + "onFinish": { + "url": "", + "enabled": false + } + }, + "enabled": true + } + } +} \ No newline at end of file diff --git a/e2e/tests/002-upload-showfile.spec.ts b/e2e/tests/002-upload-showfile.spec.ts new file mode 100644 index 000000000..bccfcea48 --- /dev/null +++ b/e2e/tests/002-upload-showfile.spec.ts @@ -0,0 +1,27 @@ +import { test } from '@playwright/test'; + +test('test', async ({ page }) => { + await page.goto('http://localhost:4001/editor'); + await page.getByRole('button', { name: 'Event...' }).click(); + await page.getByRole('menuitem', { name: 'Delete all events' }).click(); + + await page.getByRole('button', { name: 'Upload showfile' }).click(); + await page.getByLabel('Select file to upload').click(); + await page.getByLabel('Select file to upload').setInputFiles('e2e/tests/fixtures/test-db.json'); + await page.getByRole('button', { name: 'Upload' }).click(); + await page.getByRole('button', { name: 'Close' }).click(); + + // asset test events + await page.getByPlaceholder('Start').first().click(); + await page.getByText('First test event').click(); + await page.getByTestId('delay-input').click(); + await page + .locator('div') + .filter({ hasText: /^SED\+10minNew start: 10:10:00$/ }) + .getByPlaceholder('Start') + .click(); + await page.getByText('+10minNew start: 10:10:00').click(); + await page.getByText('Second test event').click(); + await page.getByText('Lunch').click(); + await page.getByText('Third test event').click(); +}); diff --git a/e2e/tests/fixtures/test-db.json b/e2e/tests/fixtures/test-db.json new file mode 100644 index 000000000..8cf5a675b --- /dev/null +++ b/e2e/tests/fixtures/test-db.json @@ -0,0 +1,183 @@ +{ + "rundown": [ + { + "title": "First test event", + "subtitle": "", + "presenter": "", + "note": "", + "endAction": "continue", + "timerType": "count-down", + "timeStart": 32400000, + "timeEnd": 36000000, + "duration": 3600000, + "isPublic": true, + "skip": false, + "colour": "", + "user0": "", + "user1": "", + "user2": "", + "user3": "", + "user4": "", + "user5": "", + "user6": "", + "user7": "", + "user8": "", + "user9": "", + "type": "event", + "revision": 4, + "id": "aa42f" + }, + { + "duration": 600000, + "type": "delay", + "revision": 0, + "id": "b1d5a" + }, + { + "title": "Second test event", + "subtitle": "", + "presenter": "", + "note": "", + "endAction": "continue", + "timerType": "count-down", + "timeStart": 36000000, + "timeEnd": 39600000, + "duration": 3600000, + "isPublic": true, + "skip": false, + "colour": "", + "user0": "", + "user1": "", + "user2": "", + "user3": "", + "user4": "", + "user5": "", + "user6": "", + "user7": "", + "user8": "", + "user9": "", + "type": "event", + "revision": 2, + "id": "d71bc" + }, + { + "title": "Lunch", + "type": "block", + "id": "91682" + }, + { + "title": "Third test event", + "subtitle": "", + "presenter": "", + "note": "", + "endAction": "continue", + "timerType": "count-down", + "timeStart": 39600000, + "timeEnd": 720000, + "duration": 47520000, + "isPublic": true, + "skip": false, + "colour": "", + "user0": "", + "user1": "", + "user2": "", + "user3": "", + "user4": "", + "user5": "", + "user6": "", + "user7": "", + "user8": "", + "user9": "", + "type": "event", + "revision": 2, + "id": "da5b4" + } + ], + "eventData": { + "title": "All about Carlos demo event", + "publicUrl": "www.getontime.no", + "publicInfo": "WiFi: demoproject \nPassword: ontimeproject", + "backstageUrl": "www.getontime.no", + "backstageInfo": "WiFi: demobackstage\nPassword: ontimeproject", + "endMessage": "" + }, + "settings": { + "app": "ontime", + "version": 2, + "serverPort": 4001, + "lock": null, + "pinCode": "1234", + "timeFormat": "24" + }, + "viewSettings": { + "overrideStyles": false + }, + "aliases": [ + { + "id": "0b0b3", + "enabled": true, + "alias": "test", + "pathAndParams": "lower?bg=ff2&text=f00&size=0.6&transition=5" + } + ], + "userFields": { + "user0": "user0", + "user1": "user1", + "user2": "user2", + "user3": "user3", + "user4": "user4", + "user5": "user5", + "user6": "user6", + "user7": "user7", + "user8": "user8", + "user9": "user9" + }, + "osc": { + "portIn": 8888, + "portOut": 9999, + "targetIP": "127.0.0.1", + "enabledIn": false, + "enabledOut": false, + "subscriptions": { + "onLoad": [], + "onStart": [], + "onPause": [], + "onStop": [], + "onUpdate": [], + "onFinish": [] + } + }, + "http": { + "http": { + "user": null, + "pwd": null, + "messages": { + "onLoad": { + "url": "", + "enabled": false + }, + "onStart": { + "url": "", + "enabled": false + }, + "onUpdate": { + "url": "", + "enabled": false + }, + "onPause": { + "url": "", + "enabled": false + }, + "onStop": { + "url": "", + "enabled": false + }, + "onFinish": { + "url": "", + "enabled": false + } + }, + "enabled": true + } + } +} \ No newline at end of file diff --git a/e2e/tests/utils/uploadTestShowfile.ts b/e2e/tests/utils/uploadTestShowfile.ts new file mode 100644 index 000000000..6a6fa33e1 --- /dev/null +++ b/e2e/tests/utils/uploadTestShowfile.ts @@ -0,0 +1,25 @@ +import fs from 'fs'; +import path from 'path'; + +// haven't been able to get this to work +// it is well discussed in the issues, so should be able to find something +// https://playwrightsolutions.com/making-a-post/ +// https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-fetch +// the upload is accepted by backend we receive 400 after upload and parse + +export async function uploadTestDb(request) { + const filePath = path.resolve('e2e/tests/fixtures/test-db.json'); + const file = fs.readFileSync(filePath); + + const response = await request.post('http://localhost:4001/ontime/db?onlyRundown=false', { + multipart: { + file: { + fileName: filePath, + mimeType: "application/json", + buffer: file, + }, + }, + }); + + return response; +} diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index 2104ca432..4b7129927 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -1,3 +1,6 @@ { - "sourceType": "module" + "sourceType": "module", + "compilerOptions": { + "esModuleInterop": true + } } diff --git a/package.json b/package.json index 0cc6a7bc1..daa3c88fe 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,12 @@ "dist-win": "turbo run dist-win", "dist-mac": "turbo run dist-mac", "dist-linux": "turbo run dist-linux", - "test": "vitest", "e2e": "cross-env DEBUG=pw:webserver npx playwright test -c playwright.config.ts", + "e2e:i": "npx playwright codegen", "cleanup": "rm -rf node_modules && rm -rf **/node_modules && rm -rf **/**/node_modules" }, "devDependencies": { - "@playwright/test": "^1.32.1", + "@playwright/test": "^1.32.3", "@types/node": "^18.11.18", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 897dc4e0c..61f28ee5f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,7 +4,7 @@ importers: .: specifiers: - '@playwright/test': ^1.32.1 + '@playwright/test': ^1.32.3 '@types/node': ^18.11.18 '@typescript-eslint/eslint-plugin': ^5.48.0 '@typescript-eslint/parser': ^5.48.0 @@ -16,7 +16,7 @@ importers: turbo: ^1.8.8 typescript: ^4.8.3 devDependencies: - '@playwright/test': 1.32.1 + '@playwright/test': 1.32.3 '@types/node': 18.11.18 '@typescript-eslint/eslint-plugin': 5.48.0_k73wpmdolxikpyqun3p36akaaq '@typescript-eslint/parser': 5.48.0_iukboom6ndih5an6iafl45j2fe @@ -2273,7 +2273,7 @@ packages: '@jest/schemas': 29.0.0 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.11.18 + '@types/node': 18.15.11 '@types/yargs': 17.0.19 chalk: 4.1.2 dev: true @@ -2363,13 +2363,13 @@ packages: fastq: 1.15.0 dev: true - /@playwright/test/1.32.1: - resolution: {integrity: sha512-FTwjCuhlm1qHUGf4hWjfr64UMJD/z0hXYbk+O387Ioe6WdyZQ+0TBDAc6P+pHjx2xCv1VYNgrKbYrNixFWy4Dg==} + /@playwright/test/1.32.3: + resolution: {integrity: sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==} engines: {node: '>=14'} hasBin: true dependencies: - '@types/node': 18.11.18 - playwright-core: 1.32.1 + '@types/node': 18.15.11 + playwright-core: 1.32.3 optionalDependencies: fsevents: 2.3.2 dev: true @@ -2826,7 +2826,7 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 18.11.18 + '@types/node': 18.15.11 dev: true /@types/cacheable-request/6.0.3: @@ -2834,7 +2834,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 16.18.23 + '@types/node': 18.15.11 '@types/responselike': 1.0.0 dev: true @@ -2867,7 +2867,7 @@ packages: /@types/connect/3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 18.11.18 + '@types/node': 18.15.11 dev: true /@types/debug/4.1.7: @@ -2883,7 +2883,7 @@ packages: /@types/express-serve-static-core/4.17.33: resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} dependencies: - '@types/node': 18.11.18 + '@types/node': 18.15.11 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 dev: true @@ -2946,7 +2946,7 @@ packages: /@types/keyv/3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 16.18.23 + '@types/node': 18.15.11 dev: true /@types/lodash.mergewith/4.6.7: @@ -3043,7 +3043,7 @@ packages: /@types/responselike/1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 16.18.23 + '@types/node': 18.15.11 dev: true /@types/scheduler/0.16.2: @@ -3057,7 +3057,7 @@ packages: resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==} dependencies: '@types/mime': 3.0.1 - '@types/node': 18.11.18 + '@types/node': 18.15.11 dev: true /@types/stack-utils/2.0.1: @@ -3096,7 +3096,7 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 16.18.23 + '@types/node': 18.15.11 dev: true optional: true @@ -6160,7 +6160,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.3.1 - '@types/node': 18.11.18 + '@types/node': 18.15.11 chalk: 4.1.2 ci-info: 3.7.1 graceful-fs: 4.2.11 @@ -7005,8 +7005,8 @@ packages: pathe: 1.1.0 dev: true - /playwright-core/1.32.1: - resolution: {integrity: sha512-KZYUQC10mXD2Am1rGlidaalNGYk3LU1vZqqNk0gT4XPty1jOqgup8KDP8l2CUlqoNKhXM5IfGjWgW37xvGllBA==} + /playwright-core/1.32.3: + resolution: {integrity: sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg==} engines: {node: '>=14'} hasBin: true dev: true