From 3ea6a939043513da64bb73fd5273a3f4ed1ef9a7 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 13 Oct 2020 02:11:30 +0800 Subject: [PATCH] server: initial tests for torrents endpoints --- .github/workflows/test.yml | 4 +- package.json | 4 +- server/.jest/test.setup.js | 36 +++++++++++- server/routes/api/index.ts | 8 +++ server/routes/api/torrents.test.ts | 92 ++++++++++++++++++++++++++++++ server/routes/api/torrents.ts | 19 ++++++ server/services/torrentService.ts | 10 ++-- 7 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 server/routes/api/torrents.test.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f6dafe26..ba95ba11 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ on: jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: @@ -23,6 +23,8 @@ jobs: with: node-version: ${{ matrix.node }} + - run: sudo apt-get install -y rtorrent + - run: npm ci - run: npm run build - run: npm run start -- --help diff --git a/package.json b/package.json index 6e02ac40..1285eadc 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,8 @@ "start:development:server": "NODE_ENV=development TS_NODE_PROJECT=server/tsconfig.json ts-node-dev --transpile-only server/bin/start.ts", "start:production": "UPDATED_SCRIPT=start npm run deprecated-warning && npm start", "start:watch": "UPDATED_SCRIPT=start:development:client npm run deprecated-warning && npm run start:development:client", - "test": "jest", - "test:watch": "jest --watchAll" + "test": "jest --forceExit", + "test:watch": "jest --watchAll --forceExit" }, "dependencies": { "argon2-browser": "^1.15.1", diff --git a/server/.jest/test.setup.js b/server/.jest/test.setup.js index 3b62cead..4dcf74b9 100644 --- a/server/.jest/test.setup.js +++ b/server/.jest/test.setup.js @@ -2,12 +2,44 @@ import crypto from 'crypto'; import fs from 'fs'; import os from 'os'; import path from 'path'; +import {spawn} from 'child_process'; const temporaryRuntimeDirectory = path.resolve(os.tmpdir(), crypto.randomBytes(12).toString('hex')); +const rTorrentSession = path.join(temporaryRuntimeDirectory, '.session'); +const rTorrentSocket = path.join(temporaryRuntimeDirectory, 'rtorrent.sock'); + +fs.mkdirSync(rTorrentSession, {recursive: true}); + +const rTorrentProcess = spawn( + 'rtorrent', + [ + '-n', + '-d', + temporaryRuntimeDirectory, + '-s', + rTorrentSession, + '-o', + 'system.daemon.set=true', + '-o', + `network.scgi.open_local=${rTorrentSocket}`, + ], + { + stdio: 'ignore', + killSignal: 'SIGKILL', + }, +); + process.argv = ['node', 'flood']; process.argv.push('--rundir', temporaryRuntimeDirectory); process.argv.push('--noauth'); -process.argv.push('--rtsocket', '/home/download/rtorrent.sock'); +process.argv.push('--rtsocket', rTorrentSocket); -afterAll(() => fs.rmdirSync(temporaryRuntimeDirectory, {recursive: true})); +afterAll((done) => { + rTorrentProcess.on('close', () => { + fs.rmdirSync(temporaryRuntimeDirectory, {recursive: true}); + done(); + }); + + rTorrentProcess.kill('SIGKILL'); +}); diff --git a/server/routes/api/index.ts b/server/routes/api/index.ts index 80ff40d3..040c8763 100644 --- a/server/routes/api/index.ts +++ b/server/routes/api/index.ts @@ -29,6 +29,14 @@ router.use('/feed-monitor', feedMonitorRoutes); router.use('/torrents', torrentsRoutes); +/** + * GET /api/activity-stream + * @summary Subscribes to activity stream + * @tags Flood + * @security User + * @return {EventSource} 200 - success response - text/event-stream + * @return {Error} 500 - failure response - application/json + */ router.get('/activity-stream', eventStream, clientActivityStream); router.get('/directory-list', (req, res) => { diff --git a/server/routes/api/torrents.test.ts b/server/routes/api/torrents.test.ts new file mode 100644 index 00000000..15567eac --- /dev/null +++ b/server/routes/api/torrents.test.ts @@ -0,0 +1,92 @@ +import fs from 'fs'; +import readline from 'readline'; +import stream from 'stream'; +import supertest from 'supertest'; + +import type {AddTorrentByURLOptions} from '../../../shared/types/api/torrents'; +import type {TorrentList, TorrentProperties} from '../../../shared/types/Torrent'; +import type {TorrentStatus} from '../../../shared/constants/torrentStatusMap'; + +import app from '../../app'; +import {getAuthToken} from './auth'; + +import {getTempPath} from '../../models/TemporaryStorage'; + +const request = supertest(app); + +const authToken = `jwt=${getAuthToken('_config')}`; + +const tempDirectory = getTempPath('rtorrent'); + +fs.mkdirSync(tempDirectory, {recursive: true}); + +jest.setTimeout(20000); + +describe('POST /api/torrents/add-urls', () => { + const addTorrentByURLOptions: AddTorrentByURLOptions = { + urls: ['https://releases.ubuntu.com/20.04/ubuntu-20.04.1-live-server-amd64.iso.torrent'], + destination: tempDirectory, + tags: ['test'], + isBasePath: false, + start: false, + }; + + const activityStream = new stream.PassThrough(); + const rl = readline.createInterface({input: activityStream}); + + const req = request.get('/api/activity-stream').send().set('Cookie', [authToken]); + + const torrentAdded = new Promise((resolve) => { + req.pipe(activityStream); + rl.on('line', (input) => { + if (input.includes('TORRENT_LIST_DIFF_CHANGE')) { + resolve(); + } + }); + }); + + it('Adds a torrent from URL', (done) => { + request + .post('/api/torrents/add-urls') + .send(addTorrentByURLOptions) + .set('Cookie', [authToken]) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end((err, _res) => { + if (err) done(err); + + done(); + }); + }); + + it('GET /api/torrents', (done) => { + torrentAdded.then(() => { + request + .get('/api/torrents') + .send() + .set('Cookie', [authToken]) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end((err, res) => { + if (err) done(err); + + expect(res.body.torrents == null).toBe(false); + const torrentList: TorrentList = res.body.torrents; + + const [torrent]: Array = Object.values(torrentList); + + expect(torrent.baseDirectory).toBe(addTorrentByURLOptions.destination); + expect(torrent.tags).toStrictEqual(addTorrentByURLOptions.tags); + + const expectedStatuses: Array = addTorrentByURLOptions.start + ? ['downloading'] + : ['stopped', 'inactive']; + expect(torrent.status).toEqual(expect.arrayContaining(expectedStatuses)); + + done(); + }); + }); + }); +}); diff --git a/server/routes/api/torrents.ts b/server/routes/api/torrents.ts index f7f1fc9a..ed433eed 100644 --- a/server/routes/api/torrents.ts +++ b/server/routes/api/torrents.ts @@ -26,6 +26,25 @@ import mediainfo from '../../util/mediainfo'; const router = express.Router(); +/** + * GET /api/torrents + * @summary Gets the list of torrents + * @tags Torrents + * @security User + * @return {TorrentListSummary} 200 - success response - application/json + * @return {Error} 500 - failure response - application/json + */ +router.get('/', (req, res) => { + const callback = ajaxUtil.getResponseFn(res); + + req.services?.torrentService + .fetchTorrentList() + .then(callback) + .catch((err) => { + callback(null, err); + }); +}); + /** * POST /api/torrents/add-urls * @summary Adds torrents by URLs. diff --git a/server/services/torrentService.ts b/server/services/torrentService.ts index f3e0b178..1928ac0a 100644 --- a/server/services/torrentService.ts +++ b/server/services/torrentService.ts @@ -118,10 +118,12 @@ class TorrentService extends BaseService { clearTimeout(this.pollTimeout); } - return this.services?.clientGatewayService - .fetchTorrentList() - .then(this.handleFetchTorrentListSuccess) - .catch(this.handleFetchTorrentListError); + return ( + this.services?.clientGatewayService + .fetchTorrentList() + .then(this.handleFetchTorrentListSuccess) + .catch(this.handleFetchTorrentListError) || Promise.reject() + ); } getTorrent(hash: TorrentProperties['hash']) {