import getPort from 'get-port'; import {spawn, exec, ChildProcess} from 'child_process'; import {rimrafSync} from 'rimraf'; import path from 'path'; import puppeteer, {Browser, Page} from 'puppeteer'; import fs from 'fs'; // @ts-expect-error no types import wait from 'wait-on'; import kill from 'tree-kill'; export interface GotifyTest { url: string; close: () => Promise; browser: Browser; page: Page; } const windowsPrefix = process.platform === 'win32' ? '.exe' : ''; const appDotGo = path.join(__dirname, '..', '..', '..', 'app.go'); const testBuildPath = path.join(__dirname, 'build'); export const newPluginDir = async (plugins: string[]): Promise => { const {dir, generator} = testPluginDir(); for (const pluginName of plugins) { await buildGoPlugin(generator(), pluginName); } return dir; }; export const newTest = async (pluginsDir = ''): Promise => { const port = await getPort(); const gotifyFile = testFilePath(); await buildGoExecutable(gotifyFile); const gotifyInstance = startGotify(gotifyFile, port, pluginsDir); const gotifyURL = 'http://localhost:' + port; await waitForGotify('http-get://localhost:' + port); const browser = await puppeteer.launch({ headless: process.env.CI === 'true', args: [`--window-size=1920,1080`, '--no-sandbox'], }); const page = await browser.newPage(); await page.setViewport({width: 1920, height: 1080}); await page.goto(gotifyURL); return { close: async () => { await Promise.all([ browser.close(), new Promise((resolve) => kill(gotifyInstance.pid!, 'SIGKILL', () => resolve(undefined)) ), ]); rimrafSync(gotifyFile, {maxRetries: 8}); }, url: gotifyURL, browser, page, }; }; const testPluginDir = (): {dir: string; generator: () => string} => { const random = Math.random().toString(36).substring(2, 15); const dirName = 'gotifyplugin_' + random; const dir = path.join(testBuildPath, dirName); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, {recursive: true, mode: 0o755}); } return { dir, generator: () => { const randomFn = Math.random().toString(36).substring(2, 15); return path.join(dir, randomFn + '.so'); }, }; }; const testFilePath = (): string => { const random = Math.random().toString(36).substring(2, 15); const filename = 'gotifytest_' + random + windowsPrefix; return path.join(testBuildPath, filename); }; const waitForGotify = (url: string): Promise => new Promise((resolve, err) => { wait({resources: [url], timeout: 40000}, (error: string) => { if (error) { console.log(error); err(error); } else { resolve(); } }); }); const buildGoPlugin = (filename: string, pluginPath: string): Promise => { process.stdout.write(`### Building Plugin ${pluginPath}\n`); return new Promise((resolve) => exec(`go build -o ${filename} -buildmode=plugin ${pluginPath}`, () => resolve()) ); }; const buildGoExecutable = (filename: string): Promise => { const envGotify = process.env.GOTIFY_EXE; if (envGotify) { if (!fs.existsSync(testBuildPath)) { fs.mkdirSync(testBuildPath, {recursive: true}); } fs.copyFileSync(envGotify, filename); process.stdout.write(`### Copying ${envGotify} to ${filename}\n`); return Promise.resolve(); } else { process.stdout.write(`### Building Gotify ${filename}\n`); return new Promise((resolve) => exec(`go build -ldflags="-X main.Mode=prod" -o ${filename} ${appDotGo}`, () => resolve() ) ); } }; const startGotify = (filename: string, port: number, pluginDir: string): ChildProcess => { const gotify = spawn(filename, [], { env: { GOTIFY_SERVER_PORT: '' + port, GOTIFY_DATABASE_CONNECTION: 'file::memory:?mode=memory&cache=shared', GOTIFY_PLUGINSDIR: pluginDir, NODE_ENV: process.env.NODE_ENV, PUBLIC_URL: process.env.PUBLIC_URL, }, }); gotify.stdout.pipe(process.stdout); gotify.stderr.pipe(process.stderr); return gotify; };