diff --git a/.github/workflows/postgres-redis-ci.yml b/.github/workflows/postgres-redis-ci.yml
index 44500f8b..6bf05b08 100644
--- a/.github/workflows/postgres-redis-ci.yml
+++ b/.github/workflows/postgres-redis-ci.yml
@@ -26,4 +26,6 @@ jobs:
env:
TEST_POSTGRES: true
timeout-minutes: 5
- run: npm test
\ No newline at end of file
+ run: npx nyc --silent npm test
+ - name: Generate coverage report
+ run: npm run cover:report
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 25260b5d..79d8a36f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,4 +47,5 @@ working
/dist/
# nyc coverage output
-.nyc_output/
\ No newline at end of file
+.nyc_output/
+coverage/
\ No newline at end of file
diff --git a/.nycrc.json b/.nycrc.json
index 76027a56..ae148ebc 100644
--- a/.nycrc.json
+++ b/.nycrc.json
@@ -1,5 +1,14 @@
{
+ "extends": "@istanbuljs/nyc-config-typescript",
+ "check-coverage": false,
+ "ski-full": true,
+ "reporter": ["text", "html"],
+ "include": [
+ "src/**/*.ts"
+ ],
"exclude": [
- "src/routes/addUnlitedVideo.ts"
+ "src/routes/addUnlistedVideo.ts",
+ "src/cronjob/downvoteSegmentArchiveJob.ts",
+ "src/databases/*"
]
}
\ No newline at end of file
diff --git a/ci.json b/ci.json
index 30e2f412..5a3ee2fb 100644
--- a/ci.json
+++ b/ci.json
@@ -4,11 +4,12 @@
"globalSalt": "testSalt",
"adminUserID": "4bdfdc9cddf2c7d07a8a87b57bf6d25389fb75d1399674ee0e0938a6a60f4c3b",
"newLeafURLs": ["placeholder"],
- "discordReportChannelWebhookURL": "http://127.0.0.1:8081/ReportChannelWebhook",
- "discordFirstTimeSubmissionsWebhookURL": "http://127.0.0.1:8081/FirstTimeSubmissionsWebhook",
- "discordCompletelyIncorrectReportWebhookURL": "http://127.0.0.1:8081/CompletelyIncorrectReportWebhook",
- "discordNeuralBlockRejectWebhookURL": "http://127.0.0.1:8081/NeuralBlockRejectWebhook",
+ "discordReportChannelWebhookURL": "http://127.0.0.1:8081/webhook/ReportChannel",
+ "discordFirstTimeSubmissionsWebhookURL": "http://127.0.0.1:8081/webhook/FirstTimeSubmissions",
+ "discordCompletelyIncorrectReportWebhookURL": "http://127.0.0.1:8081/webhook/CompletelyIncorrectReport",
+ "discordNeuralBlockRejectWebhookURL": "http://127.0.0.1:8081/webhook/NeuralBlockReject",
"neuralBlockURL": "http://127.0.0.1:8081/NeuralBlock",
+ "userCounterURL": "http://127.0.0.1:8081/UserCounter",
"behindProxy": true,
"postgres": {
"user": "ci_db_user",
@@ -70,5 +71,10 @@
"statusCode": 200
}
},
+ "patreon": {
+ "clientId": "testClientID",
+ "clientSecret": "testClientSecret",
+ "redirectUri": "http://127.0.0.1/fake/callback"
+ },
"minReputationToSubmitFiller": -1
}
diff --git a/package-lock.json b/package-lock.json
index 00e507ec..a03b338e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
"sync-mysql": "^3.0.1"
},
"devDependencies": {
+ "@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/better-sqlite3": "^7.5.0",
"@types/cron": "^2.0.0",
"@types/express": "^4.17.13",
@@ -30,8 +31,10 @@
"@types/mocha": "^9.1.1",
"@types/node": "^18.0.3",
"@types/pg": "^8.6.5",
+ "@types/sinon": "^10.0.13",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
+ "axios-mock-adapter": "^1.21.2",
"eslint": "^8.19.0",
"mocha": "^10.0.0",
"nodemon": "^2.0.19",
@@ -630,6 +633,21 @@
"node": ">=8"
}
},
+ "node_modules/@istanbuljs/nyc-config-typescript": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz",
+ "integrity": "sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w==",
+ "dev": true,
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "peerDependencies": {
+ "nyc": ">=15"
+ }
+ },
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
@@ -963,6 +981,21 @@
"@types/node": "*"
}
},
+ "node_modules/@types/sinon": {
+ "version": "10.0.13",
+ "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz",
+ "integrity": "sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/sinonjs__fake-timers": "*"
+ }
+ },
+ "node_modules/@types/sinonjs__fake-timers": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz",
+ "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==",
+ "dev": true
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz",
@@ -1339,6 +1372,19 @@
"form-data": "^4.0.0"
}
},
+ "node_modules/axios-mock-adapter": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.21.2.tgz",
+ "integrity": "sha512-jzyNxU3JzB2XVhplZboUcF0YDs7xuExzoRSHXPHr+UQajaGmcTqvkkUADgkVI2WkGlpZ1zZlMVdcTMU0ejV8zQ==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "is-buffer": "^2.0.5"
+ },
+ "peerDependencies": {
+ "axios": ">= 0.17.0"
+ }
+ },
"node_modules/babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
@@ -3094,6 +3140,29 @@
"node": ">=8"
}
},
+ "node_modules/is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -6154,6 +6223,15 @@
}
}
},
+ "@istanbuljs/nyc-config-typescript": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz",
+ "integrity": "sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w==",
+ "dev": true,
+ "requires": {
+ "@istanbuljs/schema": "^0.1.2"
+ }
+ },
"@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
@@ -6453,6 +6531,21 @@
"@types/node": "*"
}
},
+ "@types/sinon": {
+ "version": "10.0.13",
+ "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz",
+ "integrity": "sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==",
+ "dev": true,
+ "requires": {
+ "@types/sinonjs__fake-timers": "*"
+ }
+ },
+ "@types/sinonjs__fake-timers": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz",
+ "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==",
+ "dev": true
+ },
"@typescript-eslint/eslint-plugin": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz",
@@ -6698,6 +6791,16 @@
"form-data": "^4.0.0"
}
},
+ "axios-mock-adapter": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.21.2.tgz",
+ "integrity": "sha512-jzyNxU3JzB2XVhplZboUcF0YDs7xuExzoRSHXPHr+UQajaGmcTqvkkUADgkVI2WkGlpZ1zZlMVdcTMU0ejV8zQ==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.3",
+ "is-buffer": "^2.0.5"
+ }
+ },
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
@@ -7975,6 +8078,12 @@
"binary-extensions": "^2.0.0"
}
},
+ "is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
+ "dev": true
+ },
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
diff --git a/package.json b/package.json
index 511aa19e..1c8077cb 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
"main": "src/index.ts",
"scripts": {
"test": "npm run tsc && ts-node test/test.ts",
- "test:coverage": "nyc npm run test",
+ "cover": "nyc npm test",
+ "cover:report": "nyc report",
"dev": "nodemon",
"dev:bash": "nodemon -x 'npm test ; npm start'",
"postgres:docker": "docker run --rm -p 5432:5432 -e POSTGRES_USER=ci_db_user -e POSTGRES_PASSWORD=ci_db_pass postgres:alpine",
@@ -32,6 +33,7 @@
"sync-mysql": "^3.0.1"
},
"devDependencies": {
+ "@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/better-sqlite3": "^7.5.0",
"@types/cron": "^2.0.0",
"@types/express": "^4.17.13",
@@ -39,8 +41,10 @@
"@types/mocha": "^9.1.1",
"@types/node": "^18.0.3",
"@types/pg": "^8.6.5",
+ "@types/sinon": "^10.0.13",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
+ "axios-mock-adapter": "^1.21.2",
"eslint": "^8.19.0",
"mocha": "^10.0.0",
"nodemon": "^2.0.19",
diff --git a/src/app.ts b/src/app.ts
index f68507e6..caee4fab 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -200,6 +200,7 @@ function setupRoutes(router: Router) {
router.get("/api/generateToken/:type", generateTokenRequest);
router.get("/api/verifyToken", verifyTokenRequest);
+ /* istanbul ignore next */
if (config.postgres?.enabled) {
router.get("/database", (req, res) => dumpDatabase(req, res, true));
router.get("/database.json", (req, res) => dumpDatabase(req, res, false));
diff --git a/src/routes/deleteLockCategories.ts b/src/routes/deleteLockCategories.ts
index 10a3ec9c..3340f10a 100644
--- a/src/routes/deleteLockCategories.ts
+++ b/src/routes/deleteLockCategories.ts
@@ -35,6 +35,7 @@ export async function deleteLockCategoriesEndpoint(req: DeleteLockCategoriesRequ
|| !categories
|| !Array.isArray(categories)
|| categories.length === 0
+ || actionTypes && !Array.isArray(actionTypes)
|| actionTypes.length === 0
) {
return res.status(400).json({
@@ -48,7 +49,7 @@ export async function deleteLockCategoriesEndpoint(req: DeleteLockCategoriesRequ
if (!userIsVIP) {
return res.status(403).json({
- message: "Must be a VIP to mark videos.",
+ message: "Must be a VIP to lock videos.",
});
}
diff --git a/src/routes/generateToken.ts b/src/routes/generateToken.ts
index 294a9485..d617eccd 100644
--- a/src/routes/generateToken.ts
+++ b/src/routes/generateToken.ts
@@ -24,6 +24,7 @@ export async function generateTokenRequest(req: GenerateTokenRequest, res: Respo
if (type === TokenType.patreon || (type === TokenType.local && adminUserIDHash === config.adminUserID)) {
const licenseKey = await createAndSaveToken(type, code);
+ /* istanbul ignore else */
if (licenseKey) {
return res.status(200).send(`
@@ -45,5 +46,7 @@ export async function generateTokenRequest(req: GenerateTokenRequest, res: Respo
`);
}
+ } else {
+ return res.sendStatus(403);
}
}
\ No newline at end of file
diff --git a/src/routes/getDaysSavedFormatted.ts b/src/routes/getDaysSavedFormatted.ts
index 61046e38..bd906a66 100644
--- a/src/routes/getDaysSavedFormatted.ts
+++ b/src/routes/getDaysSavedFormatted.ts
@@ -7,7 +7,11 @@ export async function getDaysSavedFormatted(req: Request, res: Response): Promis
if (row !== undefined) {
//send this result
return res.send({
- daysSaved: row.daysSaved.toFixed(2),
+ daysSaved: row.daysSaved?.toFixed(2) ?? "0",
+ });
+ } else {
+ return res.send({
+ daysSaved: 0
});
}
}
diff --git a/src/routes/getIsUserVIP.ts b/src/routes/getIsUserVIP.ts
index 09f43473..a9a3f3e8 100644
--- a/src/routes/getIsUserVIP.ts
+++ b/src/routes/getIsUserVIP.ts
@@ -21,7 +21,7 @@ export async function getIsUserVIP(req: Request, res: Response): Promise {
let hashPrefix = req.params.prefix as VideoIDHash;
- const actionTypes: ActionType[] = req.query.actionTypes
- ? JSON.parse(req.query.actionTypes as string)
- : req.query.actionType
- ? Array.isArray(req.query.actionType)
- ? req.query.actionType
- : [req.query.actionType]
- : [ActionType.Skip, ActionType.Mute];
+ let actionTypes: ActionType[] = [];
+ try {
+ actionTypes = req.query.actionTypes
+ ? JSON.parse(req.query.actionTypes as string)
+ : req.query.actionType
+ ? Array.isArray(req.query.actionType)
+ ? req.query.actionType
+ : [req.query.actionType]
+ : [ActionType.Skip, ActionType.Mute];
+ if (!Array.isArray(actionTypes)) {
+ //invalid request
+ return res.sendStatus(400);
+ }
+ } catch (err) {
+ //invalid request
+ return res.status(400).send("Invalid request: JSON parse error (actionTypes)");
+ }
if (!hashPrefixTester(req.params.prefix)) {
+
return res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix
}
hashPrefix = hashPrefix.toLowerCase() as VideoIDHash;
@@ -62,7 +73,7 @@ export async function getLockCategoriesByHash(req: Request, res: Response): Prom
if (lockedRows.length === 0 || !lockedRows[0]) return res.sendStatus(404);
// merge all locks
return res.send(mergeLocks(lockedRows, actionTypes));
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
Logger.error(err as string);
return res.sendStatus(500);
}
diff --git a/src/routes/getLockReason.ts b/src/routes/getLockReason.ts
index ef4e5a33..59ab5287 100644
--- a/src/routes/getLockReason.ts
+++ b/src/routes/getLockReason.ts
@@ -32,18 +32,24 @@ export async function getLockReason(req: Request, res: Response): Promise
possibleCategories.includes(x));
- if (!videoID || !Array.isArray(actionTypes)) {
- //invalid request
- return res.sendStatus(400);
- }
-
try {
// Get existing lock categories markers
const row = await db.prepare("all", 'SELECT "category", "reason", "actionType", "userID" from "lockCategories" where "videoID" = ?', [videoID]) as {category: Category, reason: string, actionType: ActionType, userID: string }[];
@@ -115,7 +116,7 @@ export async function getLockReason(req: Request, res: Response): Promise, pag
);
if (sortBy !== SortableFields.timeSubmitted) {
+ /* istanbul ignore next */
filteredSegments.sort((a,b) => {
const key = sortDir === "desc" ? 1 : -1;
if (a[sortBy] < b[sortBy]) {
@@ -187,6 +183,7 @@ async function endpoint(req: Request, res: Response): Promise {
return res.send(segmentResponse);
}
} catch (err) {
+ /* istanbul ignore next */
if (err instanceof SyntaxError) {
return res.status(400).send("Invalid array in parameters");
} else return res.sendStatus(500);
diff --git a/src/routes/getSegmentInfo.ts b/src/routes/getSegmentInfo.ts
index d7893365..d4df1aaf 100644
--- a/src/routes/getSegmentInfo.ts
+++ b/src/routes/getSegmentInfo.ts
@@ -7,7 +7,7 @@ const isValidSegmentUUID = (str: string): boolean => /^([a-f0-9]{64}|[a-f0-9]{8}
async function getSegmentFromDBByUUID(UUID: SegmentUUID): Promise {
try {
return await db.prepare("get", `SELECT * FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]);
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
return null;
}
}
@@ -62,7 +62,7 @@ async function endpoint(req: Request, res: Response): Promise {
//send result
return res.send(DBSegments);
}
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
if (err instanceof SyntaxError) { // catch JSON.parse error
return res.status(400).send("UUIDs parameter does not match format requirements.");
} else return res.sendStatus(500);
diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts
index 7da485e6..6c938225 100644
--- a/src/routes/getSkipSegments.ts
+++ b/src/routes/getSkipSegments.ts
@@ -107,7 +107,7 @@ async function getSegmentsByVideoID(req: Request, videoID: VideoID, categories:
}
return processedSegments;
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
if (err) {
Logger.error(err as string);
return null;
@@ -169,7 +169,7 @@ async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash,
}));
return segments;
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
Logger.error(err as string);
return null;
}
@@ -465,7 +465,7 @@ async function endpoint(req: Request, res: Response): Promise {
//send result
return res.send(segments);
}
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
if (err instanceof SyntaxError) {
return res.status(400).send("Categories parameter does not match format requirements.");
} else return res.sendStatus(500);
diff --git a/src/routes/getSkipSegmentsByHash.ts b/src/routes/getSkipSegmentsByHash.ts
index 663fd6dc..454f2329 100644
--- a/src/routes/getSkipSegmentsByHash.ts
+++ b/src/routes/getSkipSegmentsByHash.ts
@@ -67,8 +67,6 @@ export async function getSkipSegmentsByHash(req: Request, res: Response): Promis
// Get all video id's that match hash prefix
const segments = await getSegmentsByHash(req, hashPrefix, categories, actionTypes, requiredSegments, service);
- if (!segments) return res.status(404).json([]);
-
const output = Object.entries(segments).map(([videoID, data]) => ({
videoID,
hash: data.hash,
diff --git a/src/routes/getStatus.ts b/src/routes/getStatus.ts
index 3f50fa15..6315fdfe 100644
--- a/src/routes/getStatus.ts
+++ b/src/routes/getStatus.ts
@@ -18,7 +18,7 @@ export async function getStatus(req: Request, res: Response): Promise
processTime = Date.now() - dbStartTime;
return e.value;
})
- .catch(e => {
+ .catch(e => /* istanbul ignore next */ {
Logger.error(`status: SQL query timed out: ${e}`);
return -1;
});
@@ -28,7 +28,7 @@ export async function getStatus(req: Request, res: Response): Promise
.then(e => {
redisProcessTime = Date.now() - redisStartTime;
return e;
- }).catch(e => {
+ }).catch(e => /* istanbul ignore next */ {
Logger.error(`status: redis increment timed out ${e}`);
return [-1];
});
@@ -36,7 +36,7 @@ export async function getStatus(req: Request, res: Response): Promise
const statusValues: Record = {
uptime: process.uptime(),
- commit: (global as any).HEADCOMMIT || "unknown",
+ commit: (global as any)?.HEADCOMMIT ?? "unknown",
db: Number(dbVersion),
startTime,
processTime,
@@ -48,7 +48,7 @@ export async function getStatus(req: Request, res: Response): Promise
activeRedisRequests: getRedisActiveRequests(),
};
return value ? res.send(JSON.stringify(statusValues[value])) : res.send(statusValues);
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
Logger.error(err as string);
return res.sendStatus(500);
}
diff --git a/src/routes/getTopUsers.ts b/src/routes/getTopUsers.ts
index a40981ea..552aaf94 100644
--- a/src/routes/getTopUsers.ts
+++ b/src/routes/getTopUsers.ts
@@ -75,11 +75,6 @@ export async function getTopUsers(req: Request, res: Response): Promise {
try {
return db.prepare("all", `SELECT "userName", "userID" from "userNames" WHERE "userName" = ? LIMIT 10`, [userName]);
- } catch (err) {
+ } catch (err) /* istanbul ignore next */{
return null;
}
}
@@ -42,6 +42,7 @@ export async function getUserID(req: Request, res: Response): Promise
: await getFuzzyUserID(userName);
if (results === undefined || results === null) {
+ /* istanbul ignore next */
return res.sendStatus(500);
} else if (results.length === 0) {
return res.sendStatus(404);
diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts
index 62ac1313..70f34641 100644
--- a/src/routes/getUserInfo.ts
+++ b/src/routes/getUserInfo.ts
@@ -28,7 +28,7 @@ async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ min
segmentCount: 0,
};
}
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
return null;
}
}
@@ -37,7 +37,7 @@ async function dbGetIgnoredSegmentCount(userID: HashedUserID): Promise {
try {
const row = await db.prepare("get", `SELECT COUNT(*) as "ignoredSegmentCount" FROM "sponsorTimes" WHERE "userID" = ? AND ( "votes" <= -2 OR "shadowHidden" = 1 )`, [userID], { useReplica: true });
return row?.ignoredSegmentCount ?? 0;
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
return null;
}
}
@@ -46,7 +46,7 @@ async function dbGetUsername(userID: HashedUserID) {
try {
const row = await db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [userID]);
return row?.userName ?? userID;
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
return false;
}
}
@@ -55,7 +55,7 @@ async function dbGetViewsForUser(userID: HashedUserID) {
try {
const row = await db.prepare("get", `SELECT SUM("views") as "viewCount" FROM "sponsorTimes" WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, [userID], { useReplica: true });
return row?.viewCount ?? 0;
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
return false;
}
}
@@ -64,7 +64,7 @@ async function dbGetIgnoredViewsForUser(userID: HashedUserID) {
try {
const row = await db.prepare("get", `SELECT SUM("views") as "ignoredViewCount" FROM "sponsorTimes" WHERE "userID" = ? AND ( "votes" <= -2 OR "shadowHidden" = 1 )`, [userID], { useReplica: true });
return row?.ignoredViewCount ?? 0;
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
return false;
}
}
@@ -73,7 +73,7 @@ async function dbGetWarningsForUser(userID: HashedUserID): Promise {
try {
const row = await db.prepare("get", `SELECT COUNT(*) as total FROM "warnings" WHERE "userID" = ? AND "enabled" = 1`, [userID], { useReplica: true });
return row?.total ?? 0;
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
Logger.error(`Couldn't get warnings for user ${userID}. returning 0`);
return 0;
}
@@ -83,7 +83,7 @@ async function dbGetLastSegmentForUser(userID: HashedUserID): Promise {
try {
const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID], { useReplica: true });
return row?.userCount > 0 ?? false;
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
return false;
}
}
@@ -194,7 +194,7 @@ async function getUserInfo(req: Request, res: Response): Promise {
export async function endpoint(req: Request, res: Response): Promise {
try {
return await getUserInfo(req, res);
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
if (err instanceof SyntaxError) { // catch JSON.parse error
return res.status(400).send("Invalid values JSON");
} else return res.sendStatus(500);
diff --git a/src/routes/getUserStats.ts b/src/routes/getUserStats.ts
index 38c8764a..b93cc177 100644
--- a/src/routes/getUserStats.ts
+++ b/src/routes/getUserStats.ts
@@ -75,7 +75,7 @@ async function dbGetUserSummary(userID: HashedUserID, fetchCategoryStats: boolea
};
}
return result;
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
Logger.error(err as string);
return null;
}
@@ -85,7 +85,7 @@ async function dbGetUsername(userID: HashedUserID) {
try {
const row = await db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [userID]);
return row?.userName ?? userID;
- } catch (err) {
+ } catch (err) /* istanbul ignore next */ {
return false;
}
}
diff --git a/src/routes/getUsername.ts b/src/routes/getUsername.ts
index 28098fea..b3a2a7b9 100644
--- a/src/routes/getUsername.ts
+++ b/src/routes/getUsername.ts
@@ -27,7 +27,7 @@ export async function getUsername(req: Request, res: Response): Promise
+ new RegExp(/[A-Za-z0-9]{40}|[A-Za-z0-9-]{35}/).test(token);
+
export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response): Promise {
const { query: { licenseKey } } = req;
if (!licenseKey) {
return res.status(400).send("Invalid request");
- }
- const licenseRegex = new RegExp(/[a-zA-Z0-9]{40}|[A-Z0-9-]{35}/);
- if (!licenseRegex.test(licenseKey)) {
+ } else if (!validatelicenseKeyRegex(licenseKey)) {
+ // fast check for invalid licence key
return res.status(200).send({
allowed: false
});
@@ -34,6 +35,7 @@ export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response)
refreshToken(TokenType.patreon, licenseKey, tokens.refreshToken).catch(Logger.error);
}
+ /* istanbul ignore else */
if (identity) {
const membership = identity.included?.[0]?.attributes;
const allowed = !!membership && ((membership.patron_status === PatronStatus.active && membership.currently_entitled_amount_cents > 0)
@@ -65,20 +67,13 @@ export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response)
async function checkAllGumroadProducts(licenseKey: string): Promise {
for (const link of config.gumroad.productPermalinks) {
try {
- const formData = new FormData();
- formData.append("product_permalink", link);
- formData.append("license_key", licenseKey);
-
- const result = await axios.request({
- url: "https://api.gumroad.com/v2/licenses/verify",
- data: formData,
- method: "POST",
- headers: formData.getHeaders()
+ const result = await axios.post("https://api.gumroad.com/v2/licenses/verify", {
+ params: { product_permalink: link, license_key: licenseKey }
});
const allowed = result.status === 200 && result.data?.success;
if (allowed) return allowed;
- } catch (e) {
+ } catch (e) /* istanbul ignore next */ {
Logger.error(`Gumroad fetch for ${link} failed: ${e}`);
}
}
diff --git a/src/utils/getIP.ts b/src/utils/getIP.ts
index 0d2dd908..85a71929 100644
--- a/src/utils/getIP.ts
+++ b/src/utils/getIP.ts
@@ -3,6 +3,9 @@ import { Request } from "express";
import { IPAddress } from "../types/segments.model";
export function getIP(req: Request): IPAddress {
+ // if in testing mode, return immediately
+ if (config.mode === "test") return "127.0.0.1" as IPAddress;
+
if (config.behindProxy === true || config.behindProxy === "true") {
config.behindProxy = "X-Forwarded-For";
}
@@ -15,6 +18,6 @@ export function getIP(req: Request): IPAddress {
case "X-Real-IP":
return req.headers["x-real-ip"] as IPAddress;
default:
- return (req.connection?.remoteAddress || req.socket?.remoteAddress) as IPAddress;
+ return req.socket?.remoteAddress as IPAddress;
}
}
\ No newline at end of file
diff --git a/src/utils/innerTubeAPI.ts b/src/utils/innerTubeAPI.ts
index c330e92c..475cda93 100644
--- a/src/utils/innerTubeAPI.ts
+++ b/src/utils/innerTubeAPI.ts
@@ -18,6 +18,7 @@ async function getFromITube (videoID: string): Promise {
const result = await axios.post(url, data, {
timeout: 3500
});
+ /* istanbul ignore else */
if (result.status === 200) {
return result.data.videoDetails;
} else {
@@ -39,6 +40,7 @@ export async function getPlayerData (videoID: string, ignoreCache = false): Prom
return data as innerTubeVideoDetails;
}
} catch (err) {
+ /* istanbul ignore next */
return Promise.reject(err);
}
}
diff --git a/src/utils/tokenUtils.ts b/src/utils/tokenUtils.ts
index 7c5ad3c0..e936b455 100644
--- a/src/utils/tokenUtils.ts
+++ b/src/utils/tokenUtils.ts
@@ -58,12 +58,11 @@ export async function createAndSaveToken(type: TokenType, code?: string): Promis
return licenseKey;
}
- } catch (e) {
+ break;
+ } catch (e) /* istanbul ignore next */ {
Logger.error(`token creation: ${e}`);
return null;
}
-
- break;
}
case TokenType.local: {
const licenseKey = generateToken();
@@ -74,7 +73,6 @@ export async function createAndSaveToken(type: TokenType, code?: string): Promis
return licenseKey;
}
}
-
return null;
}
@@ -102,15 +100,12 @@ export async function refreshToken(type: TokenType, licenseKey: string, refreshT
return true;
}
- } catch (e) {
+ } catch (e) /* istanbul ignore next */ {
Logger.error(`token refresh: ${e}`);
return false;
}
-
- break;
}
}
-
return false;
}
@@ -136,9 +131,8 @@ export async function getPatreonIdentity(accessToken: string): Promise db.prepare("get", `SELECT "userID" FROM "vipUsers" WHERE "userID" = ?`, [publicID]);
+
+const adminPrivateUserID = "testUserId";
+const permVIP1 = "addVIP_permaVIPOne";
+const publicPermVIP1 = getHash(permVIP1) as HashedUserID;
+const permVIP2 = "addVIP_permaVIPTwo";
+const publicPermVIP2 = getHash(permVIP2) as HashedUserID;
+const permVIP3 = "addVIP_permaVIPThree";
+const publicPermVIP3 = getHash(permVIP3) as HashedUserID;
+
+const endpoint = "/api/addUserAsVIP";
+const addUserAsVIP = (userID: string, enabled: boolean, adminUserID = adminPrivateUserID) => client({
+ method: "POST",
+ url: endpoint,
+ params: {
+ userID,
+ adminUserID,
+ enabled: String(enabled)
+ }
+});
+
+describe("addVIP test", function() {
+ it("User should not already be VIP", (done) => {
+ checkUserVIP(publicPermVIP1)
+ .then(result => {
+ assert.ok(!result);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should be able to add user as VIP", (done) => {
+ addUserAsVIP(publicPermVIP1, true)
+ .then(async res => {
+ assert.strictEqual(res.status, 200);
+ const row = await checkUserVIP(publicPermVIP1);
+ assert.ok(row);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should be able to add second user as VIP", (done) => {
+ addUserAsVIP(publicPermVIP2, true)
+ .then(async res => {
+ assert.strictEqual(res.status, 200);
+ const row = await checkUserVIP(publicPermVIP2);
+ assert.ok(row);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should return 403 with invalid adminID", (done) => {
+ addUserAsVIP(publicPermVIP1, true, "Invalid_Admin_User_ID")
+ .then(res => {
+ assert.strictEqual(res.status, 403);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should return 400 with missing adminID", (done) => {
+ client({
+ method: "POST",
+ url: endpoint,
+ params: {
+ userID: publicPermVIP1,
+ enabled: String(true)
+ }
+ })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should return 400 with missing userID", (done) => {
+ client({
+ method: "POST",
+ url: endpoint,
+ params: {
+ enabled: String(true),
+ adminUserID: adminPrivateUserID
+ }
+ })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should be able to remove VIP", (done) => {
+ addUserAsVIP(publicPermVIP1, false)
+ .then(async res => {
+ assert.strictEqual(res.status, 200);
+ const row = await checkUserVIP(publicPermVIP1);
+ assert.ok(!row);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should remove VIP if enabled is false", (done) => {
+ client({
+ method: "POST",
+ url: endpoint,
+ params: {
+ userID: publicPermVIP2,
+ adminUserID: adminPrivateUserID,
+ enabled: "invalid-text"
+ }
+ })
+ .then(async res => {
+ assert.strictEqual(res.status, 200);
+ const row = await checkUserVIP(publicPermVIP2);
+ assert.ok(!row);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should remove VIP if enabled is missing", (done) => {
+ client({
+ method: "POST",
+ url: endpoint,
+ params: {
+ userID: publicPermVIP3,
+ adminUserID: adminPrivateUserID
+ }
+ })
+ .then(async res => {
+ assert.strictEqual(res.status, 200);
+ const row = await checkUserVIP(publicPermVIP3);
+ assert.ok(!row);
+ done();
+ })
+ .catch(err => done(err));
+ });
+});
\ No newline at end of file
diff --git a/test/cases/generateVerifyToken.ts b/test/cases/generateVerifyToken.ts
new file mode 100644
index 00000000..ef02a5fc
--- /dev/null
+++ b/test/cases/generateVerifyToken.ts
@@ -0,0 +1,189 @@
+import assert from "assert";
+import { config } from "../../src/config";
+import axios from "axios";
+import { createAndSaveToken, TokenType } from "../../src/utils/tokenUtils";
+import MockAdapter from "axios-mock-adapter";
+let mock: MockAdapter;
+import * as patreon from "../mocks/patreonMock";
+import * as gumroad from "../mocks/gumroadMock";
+import { client } from "../utils/httpClient";
+import { validatelicenseKeyRegex } from "../../src/routes/verifyToken";
+
+const generateEndpoint = "/api/generateToken";
+const getGenerateToken = (type: string, code: string | null, adminUserID: string | null) => client({
+ url: `${generateEndpoint}/${type}`,
+ params: { code, adminUserID }
+});
+
+const verifyEndpoint = "/api/verifyToken";
+const getVerifyToken = (licenseKey: string | null) => client({
+ url: verifyEndpoint,
+ params: { licenseKey }
+});
+
+let patreonLicense: string;
+let localLicense: string;
+const gumroadLicense = gumroad.generateLicense();
+
+const extractLicenseKey = (data: string) => {
+ const regex = /([A-Za-z0-9]{40})/;
+ const match = data.match(regex);
+ if (!match) throw new Error("Failed to extract license key");
+ return match[1];
+};
+
+describe("generateToken test", function() {
+
+ before(function() {
+ mock = new MockAdapter(axios, { onNoMatch: "throwException" });
+ mock.onPost("https://www.patreon.com/api/oauth2/token").reply(200, patreon.fakeOauth);
+ });
+
+ after(function () {
+ mock.restore();
+ });
+
+ it("Should be able to create patreon token for active patron", function (done) {
+ mock.onGet(/identity/).reply(200, patreon.activeIdentity);
+ if (!config?.patreon) this.skip();
+ getGenerateToken("patreon", "patreon_code", "").then(res => {
+ patreonLicense = extractLicenseKey(res.data);
+ assert.ok(validatelicenseKeyRegex(patreonLicense));
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should create patreon token for invalid patron", function (done) {
+ mock.onGet(/identity/).reply(200, patreon.formerIdentityFail);
+ if (!config?.patreon) this.skip();
+ getGenerateToken("patreon", "patreon_code", "").then(res => {
+ patreonLicense = extractLicenseKey(res.data);
+ assert.ok(validatelicenseKeyRegex(patreonLicense));
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should be able to create new local token", function (done) {
+ createAndSaveToken(TokenType.local).then((licenseKey) => {
+ assert.ok(validatelicenseKeyRegex(licenseKey));
+ localLicense = licenseKey;
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should return 400 if missing code parameter", function (done) {
+ getGenerateToken("patreon", null, "").then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should return 403 if missing adminuserID parameter", function (done) {
+ getGenerateToken("local", "fake-code", null).then(res => {
+ assert.strictEqual(res.status, 403);
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should return 403 for invalid adminuserID parameter", function (done) {
+ getGenerateToken("local", "fake-code", "fakeAdminID").then(res => {
+ assert.strictEqual(res.status, 403);
+ done();
+ }).catch(err => done(err));
+ });
+});
+
+describe("verifyToken static tests", function() {
+ it("Should fast reject invalid token", function (done) {
+ getVerifyToken("00000").then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.ok(!res.data.allowed);
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should return 400 if missing code token", function (done) {
+ getVerifyToken(null).then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ }).catch(err => done(err));
+ });
+});
+
+describe("verifyToken mock tests", function() {
+
+ beforeEach(function() {
+ mock = new MockAdapter(axios, { onNoMatch: "throwException" });
+ mock.onPost("https://www.patreon.com/api/oauth2/token").reply(200, patreon.fakeOauth);
+ });
+
+ afterEach(function () {
+ mock.restore();
+ });
+
+ it("Should accept current patron", function (done) {
+ if (!config?.patreon) this.skip();
+ mock.onGet(/identity/).reply(200, patreon.activeIdentity);
+ getVerifyToken(patreonLicense).then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.ok(res.data.allowed);
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should reject nonexistent patron", function (done) {
+ if (!config?.patreon) this.skip();
+ mock.onGet(/identity/).reply(200, patreon.invalidIdentity);
+ getVerifyToken(patreonLicense).then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.ok(!res.data.allowed);
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should accept qualitying former patron", function (done) {
+ if (!config?.patreon) this.skip();
+ mock.onGet(/identity/).reply(200, patreon.formerIdentitySucceed);
+ getVerifyToken(patreonLicense).then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.ok(res.data.allowed);
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should reject unqualitifed former patron", function (done) {
+ if (!config?.patreon) this.skip();
+ mock.onGet(/identity/).reply(200, patreon.formerIdentityFail);
+ getVerifyToken(patreonLicense).then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.ok(!res.data.allowed);
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should accept real gumroad key", function (done) {
+ mock.onPost("https://api.gumroad.com/v2/licenses/verify").reply(200, gumroad.licenseSuccess);
+ getVerifyToken(gumroadLicense).then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.ok(res.data.allowed);
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should reject fake gumroad key", function (done) {
+ mock.onPost("https://api.gumroad.com/v2/licenses/verify").reply(200, gumroad.licenseFail);
+ getVerifyToken(gumroadLicense).then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.ok(!res.data.allowed);
+ done();
+ }).catch(err => done(err));
+ });
+
+ it("Should validate local license", function (done) {
+ getVerifyToken(localLicense).then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.ok(res.data.allowed);
+ done();
+ }).catch(err => done(err));
+ });
+});
diff --git a/test/cases/getDaysSavedFormatted.ts b/test/cases/getDaysSavedFormatted.ts
new file mode 100644
index 00000000..2b2ae230
--- /dev/null
+++ b/test/cases/getDaysSavedFormatted.ts
@@ -0,0 +1,27 @@
+import assert from "assert";
+import { client } from "../utils/httpClient";
+import sinon from "sinon";
+import { db } from "../../src/databases/databases";
+
+const endpoint = "/api/getDaysSavedFormatted";
+
+describe("getDaysSavedFormatted", () => {
+ it("can get days saved", async () => {
+ const result = await client({ url: endpoint });
+ assert.ok(result.data.daysSaved >= 0);
+ });
+
+ it("returns 0 days saved if no segments", async () => {
+ const stub = sinon.stub(db, "prepare").resolves(undefined);
+ const result = await client({ url: endpoint });
+ assert.ok(result.data.daysSaved >= 0);
+ stub.restore();
+ });
+
+ it("returns days saved to 2 fixed points", async () => {
+ const stub = sinon.stub(db, "prepare").resolves({ daysSaved: 1.23456789 });
+ const result = await client({ url: endpoint });
+ assert.strictEqual(result.data.daysSaved, "1.23");
+ stub.restore();
+ });
+});
\ No newline at end of file
diff --git a/test/cases/getIP.ts b/test/cases/getIP.ts
new file mode 100644
index 00000000..f761e01b
--- /dev/null
+++ b/test/cases/getIP.ts
@@ -0,0 +1,109 @@
+import sinon from "sinon";
+import { config } from "../../src/config";
+import assert from "assert";
+const mode = "production";
+let stub: sinon.SinonStub;
+let stub2: sinon.SinonStub;
+import { createRequest } from "../mocks/mockExpressRequest";
+import { getIP } from "../../src/utils/getIP";
+
+const v4RequestOptions = {
+ headers: {
+ "x-forwarded-for": "127.0.1.1",
+ "cf-connecting-ip": "127.0.1.2",
+ "x-real-ip": "127.0.1.3",
+ },
+ ip: "127.0.1.5",
+ socket: {
+ remoteAddress: "127.0.1.4"
+ }
+};
+const v6RequestOptions = {
+ headers: {
+ "x-forwarded-for": "[100::1]",
+ "cf-connecting-ip": "[100::2]",
+ "x-real-ip": "[100::3]",
+ },
+ ip: "[100::5]",
+ socket: {
+ remoteAddress: "[100::4]"
+ }
+};
+const v4MockRequest = createRequest(v4RequestOptions);
+const v6MockRequest = createRequest(v6RequestOptions);
+
+const expectedIP4 = {
+ "X-Forwarded-For": "127.0.1.1",
+ "Cloudflare": "127.0.1.2",
+ "X-Real-IP": "127.0.1.3",
+ "default": "127.0.1.4",
+};
+
+const expectedIP6 = {
+ "X-Forwarded-For": "[100::1]",
+ "Cloudflare": "[100::2]",
+ "X-Real-IP": "[100::3]",
+ "default": "[100::4]",
+};
+
+describe("getIP stubs", () => {
+ before(() => stub = sinon.stub(config, "mode").value(mode));
+ after(() => stub.restore());
+
+ it("Should return production mode if stub worked", (done) => {
+ assert.strictEqual(config.mode, mode);
+ done();
+ });
+});
+
+describe("getIP array tests", () => {
+ beforeEach(() => stub = sinon.stub(config, "mode").value(mode));
+ afterEach(() => {
+ stub.restore();
+ stub2.restore();
+ });
+
+ for (const [key, value] of Object.entries(expectedIP4)) {
+ it(`Should return correct IPv4 from ${key}`, (done) => {
+ stub2 = sinon.stub(config, "behindProxy").value(key);
+ const ip = getIP(v4MockRequest);
+ assert.strictEqual(config.behindProxy, key);
+ assert.strictEqual(ip, value);
+ done();
+ });
+ }
+
+ for (const [key, value] of Object.entries(expectedIP6)) {
+ it(`Should return correct IPv6 from ${key}`, (done) => {
+ stub2 = sinon.stub(config, "behindProxy").value(key);
+ const ip = getIP(v6MockRequest);
+ assert.strictEqual(config.behindProxy, key);
+ assert.strictEqual(ip, value);
+ done();
+ });
+ }
+});
+
+describe("getIP true tests", () => {
+ before(() => stub = sinon.stub(config, "mode").value(mode));
+ after(() => {
+ stub.restore();
+ stub2.restore();
+ });
+
+ it(`Should return correct IPv4 from with bool true`, (done) => {
+ stub2 = sinon.stub(config, "behindProxy").value(true);
+ const ip = getIP(v4MockRequest);
+ assert.strictEqual(config.behindProxy, "X-Forwarded-For");
+ assert.strictEqual(ip, expectedIP4["X-Forwarded-For"]);
+ done();
+ });
+
+ it(`Should return correct IPv4 from with string true`, (done) => {
+ stub2 = sinon.stub(config, "behindProxy").value("true");
+ const ip = getIP(v4MockRequest);
+ assert.strictEqual(config.behindProxy, "X-Forwarded-For");
+ assert.strictEqual(ip, expectedIP4["X-Forwarded-For"]);
+ done();
+ });
+});
\ No newline at end of file
diff --git a/test/cases/getLockCategoriesByHash.ts b/test/cases/getLockCategoriesByHash.ts
index 9695731e..f6b2757a 100644
--- a/test/cases/getLockCategoriesByHash.ts
+++ b/test/cases/getLockCategoriesByHash.ts
@@ -166,17 +166,77 @@ describe("getLockCategoriesByHash", () => {
.catch(err => done(err));
});
- it("Should be able to get by actionType", (done) => {
- getLockCategories(fakeHash.substring(0,5), [ActionType.Full])
+ it("should return 400 if invalid actionTypes", (done) => {
+ client.get(`${endpoint}/aaaa`, { params: { actionTypes: 3 } })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("should return 400 if invalid actionTypes JSON", (done) => {
+ client.get(`${endpoint}/aaaa`, { params: { actionTypes: "{3}" } })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should be able to get single lock", (done) => {
+ const videoID = "getLockHash2";
+ const hash = getHash(videoID, 1);
+ getLockCategories(hash.substring(0,6))
.then(res => {
assert.strictEqual(res.status, 200);
const expected = [{
- videoID: "fakehash-2",
- hash: fakeHash,
+ videoID,
+ hash,
+ categories: [
+ "preview"
+ ],
+ reason: "2-reason"
+ }];
+ assert.deepStrictEqual(res.data, expected);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should be able to get by actionType not in array", (done) => {
+ const videoID = "getLockHash2";
+ const hash = getHash(videoID, 1);
+ client.get(`${endpoint}/${hash.substring(0,6)}`, { params: { actionType: ActionType.Skip } })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const expected = [{
+ videoID,
+ hash,
+ categories: [
+ "preview"
+ ],
+ reason: "2-reason"
+ }];
+ assert.deepStrictEqual(res.data, expected);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should be able to get by no actionType", (done) => {
+ const videoID = "getLockHash2";
+ const hash = getHash(videoID, 1);
+ client.get(`${endpoint}/${hash.substring(0,6)}`)
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const expected = [{
+ videoID,
+ hash,
categories: [
- "sponsor"
+ "preview"
],
- reason: "fake2-notshown"
+ reason: "2-reason"
}];
assert.deepStrictEqual(res.data, expected);
done();
diff --git a/test/cases/getLockReason.ts b/test/cases/getLockReason.ts
index 9bbb35bc..1b3c36ba 100644
--- a/test/cases/getLockReason.ts
+++ b/test/cases/getLockReason.ts
@@ -55,6 +55,45 @@ describe("getLockReason", () => {
.catch(err => done(err));
});
+ it("Should be able to get with actionTypes array", (done) => {
+ client.get(endpoint, { params: { videoID: "getLockReason", category: "selfpromo", actionTypes: '["full"]' } })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const expected = [
+ { category: "selfpromo", locked: 1, reason: "selfpromo-reason", userID: vipUserID2, userName: vipUserName2 }
+ ];
+ assert.deepStrictEqual(res.data, expected);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should be able to get with actionType", (done) => {
+ client.get(endpoint, { params: { videoID: "getLockReason", category: "selfpromo", actionType: "full" } })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const expected = [
+ { category: "selfpromo", locked: 1, reason: "selfpromo-reason", userID: vipUserID2, userName: vipUserName2 }
+ ];
+ assert.deepStrictEqual(res.data, expected);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should be able to get with actionType array", (done) => {
+ client.get(endpoint, { params: { videoID: "getLockReason", category: "selfpromo", actionType: ["full"] } })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const expected = [
+ { category: "selfpromo", locked: 1, reason: "selfpromo-reason", userID: vipUserID2, userName: vipUserName2 }
+ ];
+ assert.deepStrictEqual(res.data, expected);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
it("Should be able to get empty locks", (done) => {
client.get(endpoint, { params: { videoID: "getLockReason", category: "intro" } })
.then(res => {
@@ -118,8 +157,10 @@ describe("getLockReason", () => {
})
.catch(err => done(err));
});
+});
- it("should return 400 if no videoID specified", (done) => {
+describe("getLockReason 400", () => {
+ it("Should return 400 with missing videoID", (done) => {
client.get(endpoint)
.then(res => {
assert.strictEqual(res.status, 400);
@@ -128,15 +169,37 @@ describe("getLockReason", () => {
.catch(err => done(err));
});
- it("should be able to get by actionType", (done) => {
- client.get(endpoint, { params: { videoID: "getLockReason", actionType: "full" } })
+ it("Should return 400 with invalid actionTypes ", (done) => {
+ client.get(endpoint, { params: { videoID: "valid-videoid", actionTypes: 3 } })
.then(res => {
- assert.strictEqual(res.status, 200);
- const expected = [
- { category: "selfpromo", locked: 1, reason: "sponsor-reason", userID: vipUserID2, userName: vipUserName2 },
- { category: "sponsor", locked: 0, reason: "", userID: "", userName: "" }
- ];
- partialDeepEquals(res.data, expected);
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 with invalid actionTypes JSON ", (done) => {
+ client.get(endpoint, { params: { videoID: "valid-videoid", actionTypes: "{3}" } })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 with invalid categories", (done) => {
+ client.get(endpoint, { params: { videoID: "valid-videoid", categories: 3 } })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 with invalid categories JSON", (done) => {
+ client.get(endpoint, { params: { videoID: "valid-videoid", categories: "{3}" } })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
diff --git a/test/cases/getSavedTimeForUser.ts b/test/cases/getSavedTimeForUser.ts
index 7ed809fb..b7e538a9 100644
--- a/test/cases/getSavedTimeForUser.ts
+++ b/test/cases/getSavedTimeForUser.ts
@@ -2,22 +2,31 @@ import { db } from "../../src/databases/databases";
import { getHash } from "../../src/utils/getHash";
import { deepStrictEqual } from "assert";
import { client } from "../utils/httpClient";
+import assert from "assert";
+
+// helpers
const endpoint = "/api/getSavedTimeForUser";
+const getSavedTimeForUser = (userID: string) => client({
+ url: endpoint,
+ params: { userID }
+});
describe("getSavedTimeForUser", () => {
- const user1 = "getSavedTimeForUserUser";
+ const user1 = "getSavedTimeForUser1";
+ const user2 = "getSavedTimeforUser2";
+ const [ start, end, views ] = [1, 11, 50];
+
before(async () => {
const startOfQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", "views", "shadowHidden") VALUES';
await db.prepare("run", `${startOfQuery}(?, ?, ?, ?, ?, ?, ?, ?, ?)`,
- ["getSavedTimeForUser", 1, 11, 2, "gstfu0", getHash(user1), 0, 50, 0]);
+ ["getSavedTimeForUser", start, end, 2, "getSavedTimeUUID0", getHash(user1), 0, views, 0]);
return;
});
-
- it("Should be able to get a 200", (done) => {
- client.get(endpoint, { params: { userID: user1 } })
+ it("Should be able to get a saved time", (done) => {
+ getSavedTimeForUser(user1)
.then(res => {
// (end-start)*minute * views
- const savedMinutes = ((11-1)/60) * 50;
+ const savedMinutes = ((end-start)/60) * views;
const expected = {
timeSaved: savedMinutes
};
@@ -26,4 +35,20 @@ describe("getSavedTimeForUser", () => {
})
.catch((err) => done(err));
});
+ it("Should return 404 if no submissions", (done) => {
+ getSavedTimeForUser(user2)
+ .then(res => {
+ assert.strictEqual(res.status, 404);
+ done();
+ })
+ .catch((err) => done(err));
+ });
+ it("Should return 400 if no userID", (done) => {
+ client({ url: endpoint })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch((err) => done(err));
+ });
});
diff --git a/test/cases/getSearchSegments.ts b/test/cases/getSearchSegments.ts
index 4e786040..6dbef4f5 100644
--- a/test/cases/getSearchSegments.ts
+++ b/test/cases/getSearchSegments.ts
@@ -80,6 +80,67 @@ describe("getSearchSegments", () => {
.catch(err => done(err));
});
+ it("Should be able to filter by category with categories string", (done) => {
+ client.get(endpoint, { params: { videoID: "searchTest0", categories: `["selfpromo"]` } })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const data = res.data;
+ const segments = data.segments;
+ assert.strictEqual(data.segmentCount, 1);
+ assert.strictEqual(data.page, 0);
+ assert.strictEqual(segments[0].UUID, "search-downvote");
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should be able to filter by category with categories array", (done) => {
+ client.get(endpoint, { params: { videoID: "searchTest0", category: ["selfpromo"] } })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const data = res.data;
+ const segments = data.segments;
+ assert.strictEqual(data.segmentCount, 1);
+ assert.strictEqual(data.page, 0);
+ assert.strictEqual(segments[0].UUID, "search-downvote");
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should be able to filter by category with actionTypes JSON", (done) => {
+ client.get(endpoint, { params: { videoID: "searchTest5", actionTypes: `["mute"]` } })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const data = res.data;
+ assert.strictEqual(data.segmentCount, 1);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should be able to filter by category with actionType array", (done) => {
+ client.get(endpoint, { params: { videoID: "searchTest5", actionType: ["mute"] } })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const data = res.data;
+ assert.strictEqual(data.segmentCount, 1);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should be able to filter by category with actionType string", (done) => {
+ client.get(endpoint, { params: { videoID: "searchTest5", actionType: "mute" } })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const data = res.data;
+ assert.strictEqual(data.segmentCount, 1);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
it("Should be able to filter by lock status", (done) => {
client.get(endpoint, { params: { videoID: "searchTest0", locked: false } })
.then(res => {
diff --git a/test/cases/getSearchSegments4xx.ts b/test/cases/getSearchSegments4xx.ts
new file mode 100644
index 00000000..708a0b0a
--- /dev/null
+++ b/test/cases/getSearchSegments4xx.ts
@@ -0,0 +1,48 @@
+import { client } from "../utils/httpClient";
+import assert from "assert";
+
+describe("getSearchSegments 4xx", () => {
+ const endpoint = "/api/searchSegments";
+
+ it("Should return 400 if no videoID", (done) => {
+ client.get(endpoint, { params: {} })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ const data = res.data;
+ assert.strictEqual(data, "videoID not specified");
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 if invalid categories", (done) => {
+ client.get(endpoint, { params: { videoID: "nullVideo", categories: 3 } })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ const data = res.data;
+ assert.strictEqual(data, "Categories parameter does not match format requirements.");
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 if invalid actionTypes", (done) => {
+ client.get(endpoint, { params: { videoID: "nullVideo", actionTypes: 3 } })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ const data = res.data;
+ assert.strictEqual(data, "actionTypes parameter does not match format requirements.");
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 404 if no segments", (done) => {
+ client.get(endpoint, { params: { videoID: "nullVideo", actionType: "chapter" } })
+ .then(res => {
+ assert.strictEqual(res.status, 404);
+ done();
+ })
+ .catch(err => done(err));
+ });
+});
diff --git a/test/cases/getSkipSegmentsByHash.ts b/test/cases/getSkipSegmentsByHash.ts
index 098f9c0e..3129f91f 100644
--- a/test/cases/getSkipSegmentsByHash.ts
+++ b/test/cases/getSkipSegmentsByHash.ts
@@ -3,7 +3,7 @@ import { partialDeepEquals, arrayPartialDeepEquals } from "../utils/partialDeepE
import { getHash } from "../../src/utils/getHash";
import { ImportMock, } from "ts-mock-imports";
import * as YouTubeAPIModule from "../../src/utils/youtubeApi";
-import { YouTubeApiMock } from "../youtubeMock";
+import { YouTubeApiMock } from "../mocks/youtubeMock";
import assert from "assert";
import { client } from "../utils/httpClient";
@@ -581,4 +581,78 @@ describe("getSkipSegmentsByHash", () => {
})
.catch(err => done(err));
});
+
+ it("Should be able to get single segment with requiredSegments", (done) => {
+ const requiredSegment1 = "fbf0af454059733c8822f6a4ac8ec568e0787f8c0a5ee915dd5b05e0d7a9a388";
+ client.get(`${endpoint}/17bf?requiredSegment=${requiredSegment1}`)
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID));
+ assert.strictEqual(data.length, 1);
+ const expected = [{
+ segments: [{
+ UUID: requiredSegment1
+ }]
+ }];
+ assert.ok(partialDeepEquals(data, expected));
+ assert.strictEqual(data[0].segments.length, 1);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 if categories are is number", (done) => {
+ client.get(`${endpoint}/17bf?categories=3`)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 if actionTypes is number", (done) => {
+ client.get(`${endpoint}/17bf?actionTypes=3`)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 if actionTypes are invalid json", (done) => {
+ client.get(`${endpoint}/17bf?actionTypes={test}`)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 if requiredSegments is number", (done) => {
+ client.get(`${endpoint}/17bf?requiredSegments=3`)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+
+ it("Should return 404 if requiredSegments is invalid json", (done) => {
+ client.get(`${endpoint}/17bf?requiredSegments={test}`)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 if requiredSegments is not present", (done) => {
+ client.get(`${endpoint}/17bf?requiredSegment=${fullCategoryVidHash}`)
+ .then(res => {
+ assert.strictEqual(res.status, 404);
+ done();
+ })
+ .catch(err => done(err));
+ });
});
diff --git a/test/cases/getStatus.ts b/test/cases/getStatus.ts
index 7f4ffaf4..f471404b 100644
--- a/test/cases/getStatus.ts
+++ b/test/cases/getStatus.ts
@@ -2,6 +2,7 @@ import assert from "assert";
import { db } from "../../src/databases/databases";
import { client } from "../utils/httpClient";
import { config } from "../../src/config";
+import sinon from "sinon";
let dbVersion: number;
describe("getStatus", () => {
@@ -122,4 +123,16 @@ describe("getStatus", () => {
})
.catch(err => done(err));
});
+
+ it("Should return commit unkown if not present", (done) => {
+ sinon.stub((global as any), "HEADCOMMIT").value(undefined);
+ client.get(`${endpoint}/commit`)
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.strictEqual(res.data, "test"); // commit should be test
+ done();
+ })
+ .catch(err => done(err));
+ sinon.restore();
+ });
});
diff --git a/test/cases/getTopUsers.ts b/test/cases/getTopUsers.ts
index 8808910d..07ef80c6 100644
--- a/test/cases/getTopUsers.ts
+++ b/test/cases/getTopUsers.ts
@@ -38,6 +38,15 @@ describe("getTopUsers", () => {
.catch(err => done(err));
});
+ it("Should return 400 if undefined sortType provided", (done) => {
+ client.get(endpoint)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
it("Should be able to get by all sortTypes", (done) => {
client.get(endpoint, { params: { sortType: 0 } })// minutesSaved
.then(res => {
diff --git a/test/cases/getTotalStats.ts b/test/cases/getTotalStats.ts
new file mode 100644
index 00000000..f95c2927
--- /dev/null
+++ b/test/cases/getTotalStats.ts
@@ -0,0 +1,17 @@
+import assert from "assert";
+import { client } from "../utils/httpClient";
+
+const endpoint = "/api/getTotalStats";
+
+describe("getTotalStats", () => {
+ it("Can get total stats", async () => {
+ const result = await client({ url: endpoint });
+ const data = result.data;
+ assert.ok(data?.userCount ?? true);
+ assert.ok(data.activeUsers >= 0);
+ assert.ok(data.apiUsers >= 0);
+ assert.ok(data.viewCount >= 0);
+ assert.ok(data.totalSubmissions >= 0);
+ assert.ok(data.minutesSaved >= 0);
+ });
+});
\ No newline at end of file
diff --git a/test/cases/getUserInfo.ts b/test/cases/getUserInfo.ts
index d4676c7a..9bcf0433 100644
--- a/test/cases/getUserInfo.ts
+++ b/test/cases/getUserInfo.ts
@@ -21,6 +21,7 @@ describe("getUserInfo", () => {
await db.prepare("run", sponsorTimesQuery, ["getUserInfo0", 0, 36000, 2,"uuid000009", getHash("getuserinfo_user_03"), 8, 10, "sponsor", "skip", 0]);
await db.prepare("run", sponsorTimesQuery, ["getUserInfo3", 1, 11, 2, "uuid000006", getHash("getuserinfo_user_02"), 6, 10, "sponsor", "skip", 0]);
await db.prepare("run", sponsorTimesQuery, ["getUserInfo4", 1, 11, 2, "uuid000010", getHash("getuserinfo_user_04"), 9, 10, "chapter", "chapter", 0]);
+ await db.prepare("run", sponsorTimesQuery, ["getUserInfo5", 1, 11, 2, "uuid000011", getHash("getuserinfo_user_05"), 9, 10, "sponsor", "skip", 0]);
const insertWarningQuery = 'INSERT INTO warnings ("userID", "issueTime", "issuerUserID", "enabled", "reason") VALUES (?, ?, ?, ?, ?)';
@@ -264,6 +265,15 @@ describe("getUserInfo", () => {
.catch(err => done(err));
});
+ it("Should throw 400 with invalid array", (done) => {
+ client.get(endpoint, { params: { userID: "x", values: 123 } })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done(); // pass
+ })
+ .catch(err => done(err));
+ });
+
it("Should return 200 on userID not found", (done) => {
client.get(endpoint, { params: { userID: "notused-userid" } })
.then(res => {
@@ -309,6 +319,30 @@ describe("getUserInfo", () => {
.catch(err => done(err));
});
+ it("Should be able to get permissions", (done) => {
+ client.get(endpoint, { params: { userID: "getuserinfo_user_01", value: "permissions" } })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ const expected = {
+ permissions: {
+ sponsor: true,
+ selfpromo: true,
+ exclusive_access: true,
+ interaction: true,
+ intro: true,
+ outro: true,
+ preview: true,
+ music_offtopic: true,
+ filler: true,
+ poi_highlight: true,
+ chapter: false,
+ },
+ };
+ assert.ok(partialDeepEquals(res.data, expected));
+ done(); // pass
+ });
+ });
+
it("Should ignore chapters for saved time calculations", (done) => {
client.get(endpoint, { params: { userID: "getuserinfo_user_04" } })
.then(res => {
diff --git a/test/cases/getUserInfoFree.ts b/test/cases/getUserInfoFree.ts
new file mode 100644
index 00000000..847a59a0
--- /dev/null
+++ b/test/cases/getUserInfoFree.ts
@@ -0,0 +1,76 @@
+import { db } from "../../src/databases/databases";
+import { getHash } from "../../src/utils/getHash";
+import assert from "assert";
+import { client } from "../utils/httpClient";
+
+describe("getUserInfo Free Chapters", () => {
+ const endpoint = "/api/userInfo";
+
+ const newQualifyUserID = "getUserInfo-Free-newQualify";
+ const vipQualifyUserID = "getUserInfo-Free-VIP";
+ const repQualifyUserID = "getUserInfo-Free-RepQualify";
+ const oldQualifyUserID = "getUserInfo-Free-OldQualify";
+ const newNoQualityUserID = "getUserInfo-Free-newNoQualify";
+ const postOldQualify = 1600000000000;
+
+ before(async () => {
+ const sponsorTimesQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", views, category, "actionType", "reputation", "shadowHidden") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
+ await db.prepare("run", sponsorTimesQuery, ["getUserInfoFree", 1, 2, 0, "uuid-guif-0", getHash(repQualifyUserID), postOldQualify, 0, "sponsor", "skip", 20, 0]);
+ await db.prepare("run", sponsorTimesQuery, ["getUserInfoFree", 1, 2, 0, "uuid-guif-1", getHash(oldQualifyUserID), 0, 0, "sponsor", "skip", 0, 0]); // submit at epoch
+ await db.prepare("run", sponsorTimesQuery, ["getUserInfoFree", 1, 2, 0, "uuid-guif-2", getHash(newQualifyUserID), postOldQualify, 0, "sponsor", "skip", 0, 0]);
+
+ await db.prepare("run", `INSERT INTO "vipUsers" ("userID") VALUES (?)`, [getHash(vipQualifyUserID)]);
+ });
+
+ const getUserInfo = (userID: string) => client.get(endpoint, { params: { userID, value: "freeChaptersAccess" } });
+
+ it("Should not get free access under new rule (newNoQualify)", (done) => {
+ getUserInfo(newNoQualityUserID)
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.strictEqual(res.data.freeChaptersAccess, false);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should get free access under new rule (newQualify)", (done) => {
+ getUserInfo(newQualifyUserID)
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.strictEqual(res.data.freeChaptersAccess, true);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should get free access (VIP)", (done) => {
+ getUserInfo(vipQualifyUserID)
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.strictEqual(res.data.freeChaptersAccess, true);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should get free access (rep)", (done) => {
+ getUserInfo(repQualifyUserID)
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.strictEqual(res.data.freeChaptersAccess, true);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should get free access (old)", (done) => {
+ getUserInfo(oldQualifyUserID)
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ assert.strictEqual(res.data.freeChaptersAccess, true);
+ done();
+ })
+ .catch(err => done(err));
+ });
+});
diff --git a/test/cases/getUsername.ts b/test/cases/getUsername.ts
new file mode 100644
index 00000000..5394434d
--- /dev/null
+++ b/test/cases/getUsername.ts
@@ -0,0 +1,53 @@
+import { getHash } from "../../src/utils/getHash";
+import { client } from "../utils/httpClient";
+import assert from "assert";
+
+// helpers
+const getUsername = (userID: string) => client({
+ url: "/api/getUsername",
+ params: { userID }
+});
+
+const postSetUserName = (userID: string, username: string) => client({
+ method: "POST",
+ url: "/api/setUsername",
+ params: {
+ userID,
+ username,
+ }
+});
+
+const userOnePrivate = "getUsername_0";
+const userOnePublic = getHash(userOnePrivate);
+const userOneUsername = "getUsername_username";
+
+describe("getUsername test", function() {
+ it("Should get back publicUserID if not set", (done) => {
+ getUsername(userOnePrivate)
+ .then(result => {
+ assert.strictEqual(result.data.userName, userOnePublic);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should be able to get username after setting", (done) => {
+ postSetUserName(userOnePrivate, userOneUsername)
+ .then(async () => {
+ const result = await getUsername(userOnePrivate);
+ const actual = result.data.userName;
+ assert.strictEqual(actual, userOneUsername);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should return 400 if no userID provided", (done) => {
+ client({
+ url: "/api/getUsername"
+ })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+});
\ No newline at end of file
diff --git a/test/cases/getViewsForUser.ts b/test/cases/getViewsForUser.ts
new file mode 100644
index 00000000..9471e18c
--- /dev/null
+++ b/test/cases/getViewsForUser.ts
@@ -0,0 +1,62 @@
+import { getHash } from "../../src/utils/getHash";
+import { db } from "../../src/databases/databases";
+import { client } from "../utils/httpClient";
+import assert from "assert";
+
+// helpers
+const endpoint = "/api/getViewsForUser";
+const getViewsForUser = (userID: string) => client({
+ url: endpoint,
+ params: { userID }
+});
+
+const getViewUserOne = "getViewUser1";
+const userOneViewsFirst = 30;
+const userOneViewsSecond = 0;
+
+const getViewUserTwo = "getViewUser2";
+const userTwoViews = 0;
+
+const getViewUserThree = "getViewUser3";
+
+
+describe("getViewsForUser", function() {
+ before(() => {
+ const insertSponsorTimeQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", views, category, "actionType", "videoDuration", "shadowHidden", "hashedVideoID") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
+ db.prepare("run", insertSponsorTimeQuery, ["getViewUserVideo", 0, 1, 0, "getViewUserVideo0", getHash(getViewUserOne), 0, userOneViewsFirst, "sponsor", "skip", 0, 0, "getViewUserVideo"]);
+ db.prepare("run", insertSponsorTimeQuery, ["getViewUserVideo", 0, 1, 0, "getViewUserVideo1", getHash(getViewUserOne), 0, userOneViewsSecond, "sponsor", "skip", 0, 0, "getViewUserVideo"]);
+ db.prepare("run", insertSponsorTimeQuery, ["getViewUserVideo", 0, 1, 0, "getViewUserVideo2", getHash(getViewUserTwo), 0, userTwoViews, "sponsor", "skip", 0, 0, "getViewUserVideo"]);
+ });
+ it("Should get back views for user one", (done) => {
+ getViewsForUser(getViewUserOne)
+ .then(result => {
+ assert.strictEqual(result.data.viewCount, userOneViewsFirst + userOneViewsSecond);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should get back views for user two", (done) => {
+ getViewsForUser(getViewUserTwo)
+ .then(result => {
+ assert.strictEqual(result.data.viewCount, userTwoViews);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should get 404 if no submissions", (done) => {
+ getViewsForUser(getViewUserThree)
+ .then(result => {
+ assert.strictEqual(result.status, 404);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("Should return 400 if no userID provided", (done) => {
+ client({ url: endpoint })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+});
\ No newline at end of file
diff --git a/test/cases/lockCategoriesHttp.ts b/test/cases/lockCategoriesHttp.ts
new file mode 100644
index 00000000..8525d462
--- /dev/null
+++ b/test/cases/lockCategoriesHttp.ts
@@ -0,0 +1,252 @@
+import assert from "assert";
+import { client } from "../utils/httpClient";
+import { getHash } from "../../src/utils/getHash";
+import { db } from "../../src/databases/databases";
+import { UserID } from "../../src/types/user.model";
+import { Category, VideoID } from "../../src/types/segments.model";
+
+interface LockCategory {
+ category: Category,
+ reason: string,
+ videoID: VideoID,
+ userID: UserID
+}
+const lockVIPUser = "lockCategoriesHttpVIPUser";
+const lockVIPUserHash = getHash(lockVIPUser);
+const endpoint = "/api/lockCategories";
+const checkLockCategories = (videoID: string): Promise => db.prepare("all", 'SELECT * FROM "lockCategories" WHERE "videoID" = ?', [videoID]);
+
+
+const goodResponse = (): any => ({
+ videoID: "test-videoid",
+ userID: "not-vip-test-userid",
+ categories: ["sponsor"],
+ actionTypes: ["skip"]
+});
+
+describe("POST lockCategories HTTP submitting", () => {
+ before(async () => {
+ const insertVipUserQuery = 'INSERT INTO "vipUsers" ("userID") VALUES (?)';
+ await db.prepare("run", insertVipUserQuery, [lockVIPUserHash]);
+ });
+
+ it("Should update the database version when starting the application", async () => {
+ const version = (await db.prepare("get", "SELECT key, value FROM config where key = ?", ["version"])).value;
+ assert.ok(version > 1);
+ });
+
+ it("should be able to add poi type category by type skip", (done) => {
+ const videoID = "add-record-poi";
+ client.post(endpoint, {
+ videoID,
+ userID: lockVIPUser,
+ categories: ["poi_highlight"],
+ actionTypes: ["skip"]
+ })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ checkLockCategories(videoID)
+ .then(result => {
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(result[0], "poi_highlight");
+ });
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should not add lock of invalid type", (done) => {
+ const videoID = "add_invalid_type";
+ client.post(endpoint, {
+ videoID,
+ userID: lockVIPUser,
+ categories: ["future_unused_invalid_type"],
+ actionTypes: ["skip"]
+ })
+ .then(res => {
+ assert.strictEqual(res.status, 200);
+ checkLockCategories(videoID)
+ .then(result => {
+ assert.strictEqual(result.length, 0);
+ });
+ done();
+ })
+ .catch(err => done(err));
+ });
+});
+
+describe("DELETE lockCategories 403/400 tests", () => {
+ it(" Should return 400 for no data", (done) => {
+ client.delete(endpoint, {})
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 for no categories", (done) => {
+ const json: any = {
+ videoID: "test",
+ userID: "test",
+ categories: [],
+ };
+ client.delete(endpoint, json)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 for no userID", (done) => {
+ const json: any = {
+ videoID: "test",
+ userID: null,
+ categories: ["sponsor"],
+ };
+
+ client.post(endpoint, json)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 for no videoID", (done) => {
+ const json: any = {
+ videoID: null,
+ userID: "test",
+ categories: ["sponsor"],
+ };
+
+ client.post(endpoint, json)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 for invalid category array", (done) => {
+ const json = {
+ videoID: "test",
+ userID: "test",
+ categories: {},
+ };
+
+ client.post(endpoint, json)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 for bad format categories", (done) => {
+ const json = {
+ videoID: "test",
+ userID: "test",
+ categories: "sponsor",
+ };
+
+ client.post(endpoint, json)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 403 if user is not VIP", (done) => {
+ const json = {
+ videoID: "test",
+ userID: "test",
+ categories: [
+ "sponsor",
+ ],
+ };
+
+ client.post(endpoint, json)
+ .then(res => {
+ assert.strictEqual(res.status, 403);
+ done();
+ })
+ .catch(err => done(err));
+ });
+});
+
+describe("manual DELETE/POST lockCategories 400 tests", () => {
+ it("DELETE Should return 400 for no data", (done) => {
+ client.delete(endpoint, { data: {} })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("POST Should return 400 for no data", (done) => {
+ client.post(endpoint, {})
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("DELETE Should return 400 for bad format categories", (done) => {
+ const data = goodResponse();
+ data.categories = "sponsor";
+ client.delete(endpoint, { data })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("POST Should return 400 for bad format categories", (done) => {
+ const data = goodResponse();
+ data.categories = "sponsor";
+ client.post(endpoint, data)
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("DELETE Should return 403 if user is not VIP", (done) => {
+ const data = goodResponse();
+ client.delete(endpoint, { data })
+ .then(res => {
+ assert.strictEqual(res.status, 403);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ it("POST Should return 403 if user is not VIP", (done) => {
+ const data = goodResponse();
+ client.post(endpoint, data)
+ .then(res => {
+ assert.strictEqual(res.status, 403);
+ done();
+ })
+ .catch(err => done(err));
+ });
+});
+
+describe("array of DELETE/POST lockCategories 400 tests", () => {
+ for (const key of [ "videoID", "userID", "categories" ]) {
+ for (const method of ["DELETE", "POST"]) {
+ it(`${method} - Should return 400 for invalid ${key}`, (done) => {
+ const data = goodResponse();
+ data[key] = null;
+ client(endpoint, { data, method })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+ }
+ }
+});
\ No newline at end of file
diff --git a/test/cases/lockCategoriesRecords.ts b/test/cases/lockCategoriesRecords.ts
index 7d6bd4a3..ebfc4218 100644
--- a/test/cases/lockCategoriesRecords.ts
+++ b/test/cases/lockCategoriesRecords.ts
@@ -266,106 +266,6 @@ describe("lockCategoriesRecords", () => {
.catch(err => done(err));
});
- it("Should return 400 for missing params", (done) => {
- client.post(endpoint, {})
- .then(res => {
- assert.strictEqual(res.status, 400);
- done();
- })
- .catch(err => done(err));
- });
-
- it("Should return 400 for no categories", (done) => {
- const json: any = {
- videoID: "test",
- userID: "test",
- categories: [],
- };
- client.post(endpoint, json)
- .then(res => {
- assert.strictEqual(res.status, 400);
- done();
- })
- .catch(err => done(err));
- });
-
- it("Should return 400 for no userID", (done) => {
- const json: any = {
- videoID: "test",
- userID: null,
- categories: ["sponsor"],
- };
-
- client.post(endpoint, json)
- .then(res => {
- assert.strictEqual(res.status, 400);
- done();
- })
- .catch(err => done(err));
- });
-
- it("Should return 400 for no videoID", (done) => {
- const json: any = {
- videoID: null,
- userID: "test",
- categories: ["sponsor"],
- };
-
- client.post(endpoint, json)
- .then(res => {
- assert.strictEqual(res.status, 400);
- done();
- })
- .catch(err => done(err));
- });
-
- it("Should return 400 object categories", (done) => {
- const json = {
- videoID: "test",
- userID: "test",
- categories: {},
- };
-
- client.post(endpoint, json)
- .then(res => {
- assert.strictEqual(res.status, 400);
- done();
- })
- .catch(err => done(err));
- });
-
- it("Should return 400 bad format categories", (done) => {
- const json = {
- videoID: "test",
- userID: "test",
- categories: "sponsor",
- };
-
- client.post(endpoint, json)
- .then(res => {
- assert.strictEqual(res.status, 400);
- done();
- })
- .catch(err => done(err));
- });
-
- it("Should return 403 if user is not VIP", (done) => {
- const json = {
- videoID: "test",
- userID: "test",
- categories: [
- "sponsor",
- ],
- };
-
- client.post(endpoint, json)
- .then(res => {
- assert.strictEqual(res.status, 403);
- done();
- })
- .catch(err => done(err));
- });
-
it("Should be able to delete a lockCategories record", (done) => {
const videoID = "delete-record";
const json = {
diff --git a/test/cases/postSkipSegments.ts b/test/cases/postSkipSegments.ts
index 17729dd4..57ebeb8a 100644
--- a/test/cases/postSkipSegments.ts
+++ b/test/cases/postSkipSegments.ts
@@ -4,7 +4,7 @@ import { partialDeepEquals, arrayDeepEquals } from "../utils/partialDeepEquals";
import { db } from "../../src/databases/databases";
import { ImportMock } from "ts-mock-imports";
import * as YouTubeAPIModule from "../../src/utils/youtubeApi";
-import { YouTubeApiMock } from "../youtubeMock";
+import { YouTubeApiMock } from "../mocks/youtubeMock";
import assert from "assert";
import { client } from "../utils/httpClient";
import { Feature } from "../../src/types/user.model";
diff --git a/test/cases/shadowBanUser.ts b/test/cases/shadowBanUser.ts
index 834382cc..a0b321c6 100644
--- a/test/cases/shadowBanUser.ts
+++ b/test/cases/shadowBanUser.ts
@@ -187,10 +187,34 @@ describe("shadowBanUser", () => {
})
.then(async res => {
assert.strictEqual(res.status, 200);
- const videoRow = await getShadowBanSegmentCategory(userID, 1);
+ const videoRow = await getShadowBanSegmentCategory(userID, 0);
const shadowRow = await getShadowBan(userID);
assert.ok(shadowRow); // ban still exists
- assert.strictEqual(videoRow.length, 1); // videos should be hidden
+ assert.strictEqual(videoRow.length, 0); // videos should be hidden
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should be able to un-shadowban user to restore old submissions", (done) => {
+ const userID = "shadowBanned4";
+ client({
+ method: "POST",
+ url: endpoint,
+ params: {
+ userID,
+ adminUserID: VIPuserID,
+ enabled: false,
+ categories: `["sponsor"]`,
+ unHideOldSubmissions: true
+ }
+ })
+ .then(async res => {
+ assert.strictEqual(res.status, 200);
+ const videoRow = await getShadowBanSegmentCategory(userID, 0);
+ const shadowRow = await getShadowBan(userID);
+ assert.ok(!shadowRow); // ban still exists
+ assert.strictEqual(videoRow.length, 1); // videos should be visible
assert.strictEqual(videoRow[0].category, "sponsor");
done();
})
diff --git a/test/cases/shadowBanUser4xx.ts b/test/cases/shadowBanUser4xx.ts
new file mode 100644
index 00000000..c9cdda9c
--- /dev/null
+++ b/test/cases/shadowBanUser4xx.ts
@@ -0,0 +1,48 @@
+import { db } from "../../src/databases/databases";
+import { getHash } from "../../src/utils/getHash";
+import assert from "assert";
+import { client } from "../utils/httpClient";
+
+const endpoint = "/api/shadowBanUser";
+
+const postShadowBan = (params: Record) => client({
+ method: "POST",
+ url: endpoint,
+ params
+});
+
+describe("shadowBanUser 4xx", () => {
+ const VIPuserID = "shadow-ban-4xx-vip";
+
+ before(async () => {
+ await db.prepare("run", `INSERT INTO "vipUsers" ("userID") VALUES(?)`, [getHash(VIPuserID)]);
+ });
+
+ it("Should return 400 if no adminUserID", (done) => {
+ const userID = "shadowBanned";
+ postShadowBan({ userID })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 400 if no userID", (done) => {
+ postShadowBan({ adminUserID: VIPuserID })
+ .then(res => {
+ assert.strictEqual(res.status, 400);
+ done();
+ })
+ .catch(err => done(err));
+ });
+
+ it("Should return 403 if not authorized", (done) => {
+ postShadowBan({ adminUserID: "notVIPUserID", userID: "shadowBanned" })
+ .then(res => {
+ assert.strictEqual(res.status, 403);
+ done();
+ })
+ .catch(err => done(err));
+ });
+});
diff --git a/test/cases/testUtils.ts b/test/cases/testUtils.ts
index 78818c87..29bfe60f 100644
--- a/test/cases/testUtils.ts
+++ b/test/cases/testUtils.ts
@@ -1,5 +1,5 @@
import assert from "assert";
-import { partialDeepEquals } from "../utils/partialDeepEquals";
+import { partialDeepEquals, mixedDeepEquals } from "../utils/partialDeepEquals";
describe("Test utils ", () => {
it("objectContain", () => {
@@ -135,4 +135,45 @@ describe("Test utils ", () => {
}
), "Did not match partial child array");
});
+ it("mixedDeepEquals exists", () => {
+ assert(!mixedDeepEquals({
+ name: "lorem",
+ values: [{
+ name: "ipsum",
+ }],
+ child: {
+ name: "dolor",
+ },
+ ignore: true
+ }, {
+ name: "lorem",
+ values: [{
+ name: "ipsum",
+ }],
+ child: {
+ name: "dolor",
+ },
+ ignore: false
+ }));
+ });
+ it("mixedDeepEquals noProperty", () => {
+ assert(!mixedDeepEquals({
+ name: "lorem",
+ values: [{
+ name: "ipsum",
+ }],
+ child: {
+ name: "dolor",
+ }
+ }, {
+ name: "lorem",
+ values: [{
+ name: "ipsum",
+ }],
+ child: {
+ name: "dolor",
+ },
+ ignore: false
+ }));
+ });
});
\ No newline at end of file
diff --git a/test/cases/tokenUtils.ts b/test/cases/tokenUtils.ts
new file mode 100644
index 00000000..4b3890eb
--- /dev/null
+++ b/test/cases/tokenUtils.ts
@@ -0,0 +1,50 @@
+import assert from "assert";
+import { config } from "../../src/config";
+import axios from "axios";
+import * as tokenUtils from "../../src/utils/tokenUtils";
+import MockAdapter from "axios-mock-adapter";
+import { validatelicenseKeyRegex } from "../../src/routes/verifyToken";
+let mock: MockAdapter;
+import * as patreon from "../mocks/patreonMock";
+
+const validateToken = validatelicenseKeyRegex;
+
+describe("tokenUtils test", function() {
+ before(function() {
+ mock = new MockAdapter(axios, { onNoMatch: "throwException" });
+ mock.onPost("https://www.patreon.com/api/oauth2/token").reply(200, patreon.fakeOauth);
+ mock.onGet(/identity/).reply(200, patreon.activeIdentity);
+ });
+
+ it("Should be able to create patreon token", function (done) {
+ if (!config?.patreon) this.skip();
+ tokenUtils.createAndSaveToken(tokenUtils.TokenType.patreon, "test_code").then((licenseKey) => {
+ assert.ok(validateToken(licenseKey));
+ done();
+ });
+ });
+ it("Should be able to create local token", (done) => {
+ tokenUtils.createAndSaveToken(tokenUtils.TokenType.local).then((licenseKey) => {
+ assert.ok(validateToken(licenseKey));
+ done();
+ });
+ });
+ it("Should be able to get patreon identity", function (done) {
+ if (!config?.patreon) this.skip();
+ tokenUtils.getPatreonIdentity("fake_access_token").then((result) => {
+ assert.deepEqual(result, patreon.activeIdentity);
+ done();
+ });
+ });
+ it("Should be able to refresh token", function (done) {
+ if (!config?.patreon) this.skip();
+ tokenUtils.refreshToken(tokenUtils.TokenType.patreon, "fake-licence-Key", "fake_refresh_token").then((result) => {
+ assert.strictEqual(result, true);
+ done();
+ });
+ });
+
+ after(function () {
+ mock.restore();
+ });
+});
\ No newline at end of file
diff --git a/test/cases/userCounter.ts b/test/cases/userCounter.ts
index 26a1adbe..8215ce11 100644
--- a/test/cases/userCounter.ts
+++ b/test/cases/userCounter.ts
@@ -3,10 +3,9 @@ import assert from "assert";
import { config } from "../../src/config";
import { getHash } from "../../src/utils/getHash";
-
describe("userCounter", () => {
- it("Should return 200", (done) => {
- if (!config.userCounterURL) return done(); // skip if no userCounterURL is set
+ it("Should return 200", function (done) {
+ if (!config.userCounterURL) this.skip(); // skip if no userCounterURL is set
axios.request({
method: "POST",
baseURL: config.userCounterURL,
diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts
index 11126b38..755735ff 100644
--- a/test/cases/voteOnSponsorTime.ts
+++ b/test/cases/voteOnSponsorTime.ts
@@ -3,7 +3,7 @@ import { db, privateDB } from "../../src/databases/databases";
import { getHash } from "../../src/utils/getHash";
import { ImportMock } from "ts-mock-imports";
import * as YouTubeAPIModule from "../../src/utils/youtubeApi";
-import { YouTubeApiMock } from "../youtubeMock";
+import { YouTubeApiMock } from "../mocks/youtubeMock";
import assert from "assert";
import { client } from "../utils/httpClient";
import { arrayDeepEquals } from "../utils/partialDeepEquals";
diff --git a/test/mocks.ts b/test/mocks.ts
index a5c578e9..d6673bfd 100644
--- a/test/mocks.ts
+++ b/test/mocks.ts
@@ -1,23 +1,24 @@
import express from "express";
import { config } from "../src/config";
import { Server } from "http";
+import { UserCounter } from "./mocks/UserCounter";
const app = express();
-app.post("/ReportChannelWebhook", (req, res) => {
+app.post("/webhook/ReportChannel", (req, res) => {
res.sendStatus(200);
});
-app.post("/FirstTimeSubmissionsWebhook", (req, res) => {
+app.post("/webhook/FirstTimeSubmissions", (req, res) => {
res.sendStatus(200);
});
-app.post("/CompletelyIncorrectReportWebhook", (req, res) => {
+app.post("/webhook/CompletelyIncorrectReport", (req, res) => {
res.sendStatus(200);
});
// Testing NeuralBlock
-app.post("/NeuralBlockRejectWebhook", (req, res) => {
+app.post("/webhook/NeuralBlockReject", (req, res) => {
res.sendStatus(200);
});
@@ -47,6 +48,9 @@ app.post("/CustomWebhook", (req, res) => {
res.sendStatus(200);
});
+// mocks
+app.use("/UserCounter", UserCounter);
+
export function createMockServer(callback: () => void): Server {
return app.listen(config.mockPort, callback);
}
diff --git a/test/mocks/UserCounter.ts b/test/mocks/UserCounter.ts
new file mode 100644
index 00000000..d4ba32a7
--- /dev/null
+++ b/test/mocks/UserCounter.ts
@@ -0,0 +1,11 @@
+import { Router } from "express";
+export const UserCounter = Router();
+
+UserCounter.post("/api/v1/addIP", (req, res) => {
+ res.sendStatus(200);
+});
+UserCounter.get("/api/v1/userCount", (req, res) => {
+ res.send({
+ userCount: 100
+ });
+});
\ No newline at end of file
diff --git a/test/mocks/gumroadMock.ts b/test/mocks/gumroadMock.ts
new file mode 100644
index 00000000..00ae8311
--- /dev/null
+++ b/test/mocks/gumroadMock.ts
@@ -0,0 +1,22 @@
+export const licenseSuccess = {
+ success: true,
+ uses: 4,
+ purchase: {}
+};
+
+export const licenseFail = {
+ success: false,
+ message: "That license does not exist for the provided product."
+};
+
+
+const subCode = (length = 8) => {
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ let result = "";
+ for (let i = 0; i < length; i++) {
+ result += characters[(Math.floor(Math.random() * characters.length))];
+ }
+ return result;
+};
+
+export const generateLicense = (): string => `${subCode()}-${subCode()}-${subCode()}-${subCode()}`;
diff --git a/test/mocks/mockExpressRequest.ts b/test/mocks/mockExpressRequest.ts
new file mode 100644
index 00000000..7ad4ae08
--- /dev/null
+++ b/test/mocks/mockExpressRequest.ts
@@ -0,0 +1,33 @@
+const nullStub = (): any => null;
+
+export const createRequest = (options: any) => ({
+ app: {},
+ baseUrl: "",
+ body: {},
+ cookies: {},
+ fresh: true,
+ headers: {},
+ hostname: "example.com",
+ ip: "",
+ ips: [],
+ method: "GET",
+ originalUrl: "/",
+ params: {},
+ path: "/",
+ protocol: "https",
+ query: {},
+ route: {},
+ secure: true,
+ signedCookies: {},
+ stale: false,
+ subdomains: [],
+ xhr: true,
+ accepts: nullStub(),
+ acceptsCharsets: nullStub(),
+ acceptsEncodings: nullStub(),
+ acceptsLanguages: nullStub(),
+ get: nullStub(),
+ is: nullStub(),
+ range: nullStub(),
+ ...options
+});
diff --git a/test/mocks/patreonMock.ts b/test/mocks/patreonMock.ts
new file mode 100644
index 00000000..aafe26e6
--- /dev/null
+++ b/test/mocks/patreonMock.ts
@@ -0,0 +1,59 @@
+export const activeIdentity = {
+ data: {},
+ links: {},
+ included: [
+ {
+ attributes: {
+ is_monthly: true,
+ currently_entitled_amount_cents: 100,
+ patron_status: "active_patron",
+ },
+ id: "id",
+ type: "campaign"
+ }
+ ],
+};
+
+export const invalidIdentity = {
+ data: {},
+ links: {},
+ included: [{}],
+};
+
+export const formerIdentitySucceed = {
+ data: {},
+ links: {},
+ included: [
+ {
+ attributes: {
+ is_monthly: true,
+ campaign_lifetime_support_cents: 500,
+ patron_status: "former_patron",
+ },
+ id: "id",
+ type: "campaign"
+ }
+ ],
+};
+
+export const formerIdentityFail = {
+ data: {},
+ links: {},
+ included: [
+ {
+ attributes: {
+ is_monthly: true,
+ campaign_lifetime_support_cents: 1,
+ patron_status: "former_patron",
+ },
+ id: "id",
+ type: "campaign"
+ }
+ ],
+};
+
+export const fakeOauth = {
+ access_token: "test_access_token",
+ refresh_token: "test_refresh_token",
+ expires_in: 3600,
+};
\ No newline at end of file
diff --git a/test/youtubeMock.ts b/test/mocks/youtubeMock.ts
similarity index 97%
rename from test/youtubeMock.ts
rename to test/mocks/youtubeMock.ts
index bb489afe..95f3d207 100644
--- a/test/youtubeMock.ts
+++ b/test/mocks/youtubeMock.ts
@@ -1,4 +1,4 @@
-import { APIVideoData, APIVideoInfo } from "../src/types/youtubeApi.model";
+import { APIVideoData, APIVideoInfo } from "../../src/types/youtubeApi.model";
export class YouTubeApiMock {
// eslint-disable-next-line require-await