diff --git a/.gitignore b/.gitignore index 3715c67..7b3c05c 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,4 @@ config.json # Mac files .DS_Store /.idea/ +/dist/ diff --git a/Dockerfile b/Dockerfile index 4dc9470..2025a0b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,13 @@ FROM node:12 WORKDIR /usr/src/app COPY package.json . -RUN npm install -COPY index.ts . +COPY package-lock.json . +COPY tsconfig.json . COPY src src +RUN npm ci +RUN npm run tsc RUN mkdir databases COPY databases/*.sql databases/ COPY entrypoint.sh . EXPOSE 8080 -CMD ./entrypoint.sh \ No newline at end of file +CMD ./entrypoint.sh diff --git a/entrypoint.sh b/entrypoint.sh index 31b838c..b684468 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -23,4 +23,4 @@ cp /etc/sponsorblock/config.json . || cat < config.json "readOnly": false } EOF -ts-node index.ts +node dist/index.js diff --git a/index.ts b/index.ts deleted file mode 100644 index b41e572..0000000 --- a/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {config} from "./src/config"; -import {initDb} from './src/databases/databases'; -import {createServer} from "./src/app"; -import {Logger} from "./src/utils/logger"; - -initDb(); -createServer(() => { - Logger.info("Server started on port " + config.port + "."); -}); diff --git a/package-lock.json b/package-lock.json index 3f5ebe1..0d8ac5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,51 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@sinonjs/commons": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/formatio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^5.0.2" + } + }, + "@sinonjs/samsam": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz", + "integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/better-sqlite3": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-5.4.0.tgz", @@ -1510,6 +1555,12 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1575,6 +1626,12 @@ "verror": "1.10.0" } }, + "just-extend": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", + "dev": true + }, "jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -1619,6 +1676,12 @@ "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -1897,6 +1960,30 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "nise": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", + "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -2432,6 +2519,29 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "sinon": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.0.tgz", + "integrity": "sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/samsam": "^5.2.0", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2561,6 +2671,23 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + } + } + }, "sync-mysql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sync-mysql/-/sync-mysql-3.0.1.tgz", @@ -2701,6 +2828,12 @@ "punycode": "^2.1.1" } }, + "ts-mock-imports": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-mock-imports/-/ts-mock-imports-1.3.0.tgz", + "integrity": "sha512-cCrVcRYsp84eDvPict0ZZD/D7ppQ0/JSx4ve6aEU8DjlsaWRJWV6ADMovp2sCuh6pZcduLFoIYhKTDU2LARo7Q==", + "dev": true + }, "ts-node": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", @@ -2735,6 +2868,12 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/package.json b/package.json index d102713..bdd33fd 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,13 @@ "name": "sponsor_block_server", "version": "0.1.0", "description": "Server that holds the SponsorBlock database", - "main": "index.ts", + "main": "src/index.ts", "scripts": { - "test": "ts-node test.ts", + "test": "ts-node test/test.ts", "dev": "nodemon -x \"(npm test || echo test failed) && npm start\"", "dev:bash": "nodemon -x 'npm test ; npm start'", - "start": "ts-node index.ts" + "start": "ts-node src/index.ts", + "tsc": "tsc -p tsconfig.json" }, "author": "Ajay Ramachandran", "license": "MIT", @@ -35,6 +36,8 @@ "@types/request": "^2.48.5", "mocha": "^7.1.1", "nodemon": "^2.0.2", + "sinon": "^9.2.0", + "ts-mock-imports": "^1.3.0", "ts-node": "^9.0.0", "typescript": "^4.0.3" } diff --git a/src/app.ts b/src/app.ts index 177d405..2d3945e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,4 @@ -import express, {Request, RequestHandler, Response} from 'express'; +import express, {Express, Request, RequestHandler, Response} from 'express'; import {config} from './config'; import {oldSubmitSponsorTimes} from './routes/oldSubmitSponsorTimes'; import {oldGetVideoSponsorTimes} from './routes/oldGetVideoSponsorTimes'; @@ -22,108 +22,112 @@ import {voteOnSponsorTime} from './routes/voteOnSponsorTime'; import {getSkipSegmentsByHash} from './routes/getSkipSegmentsByHash'; import {postSkipSegments} from './routes/postSkipSegments'; import {endpoint as getSkipSegments} from './routes/getSkipSegments'; - import {userCounter} from './middleware/userCounter'; import {loggerMiddleware} from './middleware/logger'; import {corsMiddleware} from './middleware/cors'; import {rateLimitMiddleware} from './middleware/requestRateLimit'; -// Create a service (the app object is just a callback). -const app = express(); -// Rate limit endpoint lists -const voteEndpoints: RequestHandler[] = [voteOnSponsorTime]; -const viewEndpoints: RequestHandler[] = [viewedVideoSponsorTime]; -if (config.rateLimit) { - if (config.rateLimit.vote) voteEndpoints.unshift(rateLimitMiddleware(config.rateLimit.vote)); - if (config.rateLimit.view) viewEndpoints.unshift(rateLimitMiddleware(config.rateLimit.view)); -} -//setup CORS correctly -app.use(corsMiddleware); -app.use(loggerMiddleware); -app.use(express.json()); - -if (config.userCounterURL) app.use(userCounter); - -// Setup pretty JSON -if (config.mode === "development") app.set('json spaces', 2); - -// Set production mode -app.set('env', config.mode || 'production'); - -//add the get function -app.get('/api/getVideoSponsorTimes', oldGetVideoSponsorTimes); - -//add the oldpost function -app.get('/api/postVideoSponsorTimes', oldSubmitSponsorTimes); -app.post('/api/postVideoSponsorTimes', oldSubmitSponsorTimes); - -//add the skip segments functions -app.get('/api/skipSegments', getSkipSegments); -app.post('/api/skipSegments', postSkipSegments); - -// add the privacy protecting skip segments functions -app.get('/api/skipSegments/:prefix', getSkipSegmentsByHash); - -//voting endpoint -app.get('/api/voteOnSponsorTime', ...voteEndpoints); -app.post('/api/voteOnSponsorTime', ...voteEndpoints); - -//Endpoint when a submission is skipped -app.get('/api/viewedVideoSponsorTime', ...viewEndpoints); -app.post('/api/viewedVideoSponsorTime', ...viewEndpoints); - -//To set your username for the stats view -app.post('/api/setUsername', setUsername); - -//get what username this user has -app.get('/api/getUsername', getUsername); - -//Endpoint used to hide a certain user's data -app.post('/api/shadowBanUser', shadowBanUser); - -//Endpoint used to make a user a VIP user with special privileges -app.post('/api/addUserAsVIP', addUserAsVIP); - -//Gets all the views added up for one userID -//Useful to see how much one user has contributed -app.get('/api/getViewsForUser', getViewsForUser); - -//Gets all the saved time added up (views * sponsor length) for one userID -//Useful to see how much one user has contributed -//In minutes -app.get('/api/getSavedTimeForUser', getSavedTimeForUser); - -app.get('/api/getTopUsers', getTopUsers); - -//send out totals -//send the total submissions, total views and total minutes saved -app.get('/api/getTotalStats', getTotalStats); - -app.get('/api/getUserInfo', getUserInfo); - -//send out a formatted time saved total -app.get('/api/getDaysSavedFormatted', getDaysSavedFormatted); - -//submit video containing no segments -app.post('/api/noSegments', postNoSegments); - -app.delete('/api/noSegments', deleteNoSegments); - -//get if user is a vip -app.get('/api/isUserVIP', getIsUserVIP); - -//sent user a warning -app.post('/api/warnUser', postWarning); - -//get if user is a vip -app.post('/api/segmentShift', postSegmentShift); - -app.get('/database.db', function (req: Request, res: Response) { - res.sendFile("./databases/sponsorTimes.db", {root: "./"}); -}); - -// Create an HTTP service. export function createServer(callback: () => void) { + // Create a service (the app object is just a callback). + const app = express(); + + //setup CORS correctly + app.use(corsMiddleware); + app.use(loggerMiddleware); + app.use(express.json()); + + if (config.userCounterURL) app.use(userCounter); + + // Setup pretty JSON + if (config.mode === "development") app.set('json spaces', 2); + + // Set production mode + app.set('env', config.mode || 'production'); + + setupRoutes(app); + return app.listen(config.port, callback); } + +function setupRoutes(app: Express) { + // Rate limit endpoint lists + const voteEndpoints: RequestHandler[] = [voteOnSponsorTime]; + const viewEndpoints: RequestHandler[] = [viewedVideoSponsorTime]; + if (config.rateLimit) { + if (config.rateLimit.vote) voteEndpoints.unshift(rateLimitMiddleware(config.rateLimit.vote)); + if (config.rateLimit.view) viewEndpoints.unshift(rateLimitMiddleware(config.rateLimit.view)); + } + + //add the get function + app.get('/api/getVideoSponsorTimes', oldGetVideoSponsorTimes); + + //add the oldpost function + app.get('/api/postVideoSponsorTimes', oldSubmitSponsorTimes); + app.post('/api/postVideoSponsorTimes', oldSubmitSponsorTimes); + + //add the skip segments functions + app.get('/api/skipSegments', getSkipSegments); + app.post('/api/skipSegments', postSkipSegments); + + // add the privacy protecting skip segments functions + app.get('/api/skipSegments/:prefix', getSkipSegmentsByHash); + + //voting endpoint + app.get('/api/voteOnSponsorTime', ...voteEndpoints); + app.post('/api/voteOnSponsorTime', ...voteEndpoints); + + //Endpoint when a submission is skipped + app.get('/api/viewedVideoSponsorTime', ...viewEndpoints); + app.post('/api/viewedVideoSponsorTime', ...viewEndpoints); + + //To set your username for the stats view + app.post('/api/setUsername', setUsername); + + //get what username this user has + app.get('/api/getUsername', getUsername); + + //Endpoint used to hide a certain user's data + app.post('/api/shadowBanUser', shadowBanUser); + + //Endpoint used to make a user a VIP user with special privileges + app.post('/api/addUserAsVIP', addUserAsVIP); + + //Gets all the views added up for one userID + //Useful to see how much one user has contributed + app.get('/api/getViewsForUser', getViewsForUser); + + //Gets all the saved time added up (views * sponsor length) for one userID + //Useful to see how much one user has contributed + //In minutes + app.get('/api/getSavedTimeForUser', getSavedTimeForUser); + + app.get('/api/getTopUsers', getTopUsers); + + //send out totals + //send the total submissions, total views and total minutes saved + app.get('/api/getTotalStats', getTotalStats); + + app.get('/api/getUserInfo', getUserInfo); + + //send out a formatted time saved total + app.get('/api/getDaysSavedFormatted', getDaysSavedFormatted); + + //submit video containing no segments + app.post('/api/noSegments', postNoSegments); + + app.delete('/api/noSegments', deleteNoSegments); + + //get if user is a vip + app.get('/api/isUserVIP', getIsUserVIP); + + //sent user a warning + app.post('/api/warnUser', postWarning); + + //get if user is a vip + app.post('/api/segmentShift', postSegmentShift); + + app.get('/database.db', function (req: Request, res: Response) { + res.sendFile("./databases/sponsorTimes.db", {root: "./"}); + }); +} diff --git a/src/config.ts b/src/config.ts index fa35214..7a172cc 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import {SBSConfig} from "./types/config.model"; -const isTestMode = process.env.npm_lifecycle_script === 'ts-node test.ts'; +const isTestMode = process.env.npm_lifecycle_script === 'ts-node test/test.ts'; const configFile = isTestMode ? 'test.json' : 'config.json'; export const config: SBSConfig = JSON.parse(fs.readFileSync(configFile).toString('utf8')); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..22bd0ac --- /dev/null +++ b/src/index.ts @@ -0,0 +1,9 @@ +import {config} from "./config"; +import {initDb} from './databases/databases'; +import {createServer} from "./app"; +import {Logger} from "./utils/logger"; + +initDb(); +createServer(() => { + Logger.info("Server started on port " + config.port + "."); +}); diff --git a/src/middleware/requestRateLimit.ts b/src/middleware/requestRateLimit.ts index e3756be..8489fd8 100644 --- a/src/middleware/requestRateLimit.ts +++ b/src/middleware/requestRateLimit.ts @@ -13,9 +13,5 @@ export function rateLimitMiddleware(limitConfig: RateLimitConfig): rateLimit.Rat keyGenerator: (req) => { return getHash(getIP(req), 1); }, - skip: (/*req, res*/) => { - // skip rate limit if running in test mode - return process.env.npm_lifecycle_script === 'ts-node test.ts'; - }, }); } diff --git a/src/utils/youtubeApi.ts b/src/utils/youtubeApi.ts index af71213..212b6f1 100644 --- a/src/utils/youtubeApi.ts +++ b/src/utils/youtubeApi.ts @@ -2,28 +2,15 @@ import {config} from '../config'; import {Logger} from './logger'; import * as redis from './redis'; // @ts-ignore -import YouTubeAPI from 'youtube-api'; +import _youTubeAPI from 'youtube-api'; -import {YouTubeAPI as youtubeApiTest} from '../../test/youtubeMock'; +_youTubeAPI.authenticate({ + type: "key", + key: config.youtubeAPIKey, +}); -let _youtubeApi: { - listVideos: (videoID: string, callback: (err: string | boolean, data: any) => void) => void -}; -// If in test mode, return a mocked youtube object -// otherwise return an authenticated youtube api -if (config.mode === "test") { - _youtubeApi = youtubeApiTest; -} -else { - _youtubeApi = YouTubeAPI; - - YouTubeAPI.authenticate({ - type: "key", - key: config.youtubeAPIKey, - }); - - // YouTubeAPI.videos.list wrapper with cacheing - _youtubeApi.listVideos = (videoID: string, callback: (err: string | boolean, data: any) => void) => { +export class YouTubeAPI { + static listVideos(videoID: string, callback: (err: string | boolean, data: any) => void) { const part = 'contentDetails,snippet'; if (videoID.length !== 11 || videoID.includes(".")) { callback("Invalid video ID", undefined); @@ -34,7 +21,7 @@ else { redis.get(redisKey, (getErr: string, result: string) => { if (getErr || !result) { Logger.debug("redis: no cache for video information: " + videoID); - YouTubeAPI.videos.list({ + _youTubeAPI.videos.list({ part, id: videoID, }, (ytErr: boolean | string, data: any) => { @@ -63,7 +50,3 @@ else { }); }; } - -export { - _youtubeApi as YouTubeAPI -} diff --git a/test/cases/getSegmentsByHash.ts b/test/cases/getSegmentsByHash.ts index 00b3718..9bf4c89 100644 --- a/test/cases/getSegmentsByHash.ts +++ b/test/cases/getSegmentsByHash.ts @@ -2,8 +2,13 @@ import request from 'request'; import {db} from '../../src/databases/databases'; import {Done, getbaseURL} from '../utils'; import {getHash} from '../../src/utils/getHash'; +import {ImportMock,} from 'ts-mock-imports'; +import * as YouTubeAPIModule from '../../src/utils/youtubeApi'; +import {YouTubeApiMock} from '../youtubeMock'; - +const mockManager = ImportMock.mockStaticClass(YouTubeAPIModule, 'YouTubeAPI'); +const sinonStub = mockManager.mock('listVideos'); +sinonStub.callsFake(YouTubeApiMock.listVideos); describe('getSegmentsByHash', () => { before(() => { @@ -12,6 +17,7 @@ describe('getSegmentsByHash', () => { db.exec(startOfQuery + "('getSegmentsByHash-0', 20, 30, 2, 'getSegmentsByHash-0-1', 'testman', 100, 150, 'intro', 0, '" + getHash('getSegmentsByHash-0', 1) + "')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 db.exec(startOfQuery + "('getSegmentsByHash-noMatchHash', 40, 50, 2, 'getSegmentsByHash-noMatchHash', 'testman', 0, 50, 'sponsor', 0, 'fdaffnoMatchHash')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 db.exec(startOfQuery + "('getSegmentsByHash-1', 60, 70, 2, 'getSegmentsByHash-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('getSegmentsByHash-1', 1) + "')"); // hash = 3272fa85ee0927f6073ef6f07ad5f3146047c1abba794cfa364d65ab9921692b + }); it('Should be able to get a 200', (done: Done) => { diff --git a/test/cases/postSkipSegments.ts b/test/cases/postSkipSegments.ts index 7423c28..5c68f40 100644 --- a/test/cases/postSkipSegments.ts +++ b/test/cases/postSkipSegments.ts @@ -3,6 +3,13 @@ import {config} from '../../src/config'; import {getHash} from '../../src/utils/getHash'; import {Done, getbaseURL} from '../utils'; import {db} from '../../src/databases/databases'; +import {ImportMock} from 'ts-mock-imports'; +import * as YouTubeAPIModule from '../../src/utils/youtubeApi'; +import {YouTubeApiMock} from '../youtubeMock'; + +const mockManager = ImportMock.mockStaticClass(YouTubeAPIModule, 'YouTubeAPI'); +const sinonStub = mockManager.mock('listVideos'); +sinonStub.callsFake(YouTubeApiMock.listVideos); describe('postSkipSegments', () => { before(() => { @@ -227,7 +234,7 @@ describe('postSkipSegments', () => { if (err) done(err); else if (res.statusCode === 400) { let rows = db.prepare('all', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ? and votes > -1", ["80percent_video"]); - let success = true && rows.length == 2; + let success = rows.length == 2; for (const row of rows) { if ((row.startTime === 2000 || row.endTime === 4000 || row.category === "sponsor") || (row.startTime === 1500 || row.endTime === 2750 || row.category === "sponsor") || diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts index 329d0a1..7880cac 100644 --- a/test/cases/voteOnSponsorTime.ts +++ b/test/cases/voteOnSponsorTime.ts @@ -3,6 +3,13 @@ import {config} from '../../src/config'; import {db, privateDB} from '../../src/databases/databases'; import {Done, getbaseURL} from '../utils'; import {getHash} from '../../src/utils/getHash'; +import {ImportMock} from 'ts-mock-imports'; +import * as YouTubeAPIModule from '../../src/utils/youtubeApi'; +import {YouTubeApiMock} from '../youtubeMock'; + +const mockManager = ImportMock.mockStaticClass(YouTubeAPIModule, 'YouTubeAPI'); +const sinonStub = mockManager.mock('listVideos'); +sinonStub.callsFake(YouTubeApiMock.listVideos); describe('voteOnSponsorTime', () => { before(() => { diff --git a/test.ts b/test/test.ts similarity index 66% rename from test.ts rename to test/test.ts index f995320..7102f1e 100644 --- a/test.ts +++ b/test/test.ts @@ -1,11 +1,20 @@ import Mocha from 'mocha'; import fs from 'fs'; import path from 'path'; -import {config} from './src/config'; -import {createServer} from './src/app'; -import {createMockServer} from './test/mocks'; -import {Logger} from './src/utils/logger'; -import {initDb} from './src/databases/databases'; +import {config} from '../src/config'; +import {createServer} from '../src/app'; +import {createMockServer} from './mocks'; +import {Logger} from '../src/utils/logger'; +import {initDb} from '../src/databases/databases'; +import {ImportMock} from 'ts-mock-imports'; +import * as rateLimitMiddlewareModule from '../src/middleware/requestRateLimit'; +import rateLimit from 'express-rate-limit'; + +ImportMock.mockFunction(rateLimitMiddlewareModule, 'rateLimitMiddleware', rateLimit({ + skip: () => { + return true; + } +})); // delete old test database if (fs.existsSync(config.db)) fs.unlinkSync(config.db) diff --git a/test/youtubeMock.ts b/test/youtubeMock.ts index 5e32acc..64249e0 100644 --- a/test/youtubeMock.ts +++ b/test/youtubeMock.ts @@ -7,65 +7,63 @@ YouTubeAPI.videos.list({ // https://developers.google.com/youtube/v3/docs/videos -export const YouTubeAPI = { - listVideos: (id: string, callback: (ytErr: any, data: any) => void) => { - YouTubeAPI.videos.list({ - id: id, - }, callback); - }, - videos: { - list: (obj: { part: string; id: any } | { id: string }, callback: (ytErr: any, data: any) => void) => { - if (obj.id === "knownWrongID") { - callback(undefined, { - pageInfo: { - totalResults: 0, - }, - items: [], - }); - } - if (obj.id === "noDuration") { - callback(undefined, { - pageInfo: { - totalResults: 1, - }, - items: [ - { - contentDetails: { - duration: "PT0S", - }, - snippet: { - title: "Example Title", - thumbnails: { - maxres: { - url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", - }, + +export class YouTubeApiMock { + static listVideos(videoID: string, callback: (ytErr: any, data: any) => void) { + const obj = { + id: videoID + }; + + if (obj.id === "knownWrongID") { + callback(undefined, { + pageInfo: { + totalResults: 0, + }, + items: [], + }); + } + if (obj.id === "noDuration") { + callback(undefined, { + pageInfo: { + totalResults: 1, + }, + items: [ + { + contentDetails: { + duration: "PT0S", + }, + snippet: { + title: "Example Title", + thumbnails: { + maxres: { + url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", }, }, }, - ], - }); - } else { - callback(undefined, { - pageInfo: { - totalResults: 1, }, - items: [ - { - contentDetails: { - duration: "PT1H23M30S", - }, - snippet: { - title: "Example Title", - thumbnails: { - maxres: { - url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", - }, + ], + }); + } else { + callback(undefined, { + pageInfo: { + totalResults: 1, + }, + items: [ + { + contentDetails: { + duration: "PT1H23M30S", + }, + snippet: { + title: "Example Title", + thumbnails: { + maxres: { + url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", }, }, }, - ], - }); - } - }, - }, -}; + }, + ], + }); + } + } +} diff --git a/tsconfig.json b/tsconfig.json index d00f210..f6ee0c9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,7 @@ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ @@ -76,5 +76,8 @@ /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } + }, + "include": [ + "./src/**/*" + ] }