mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2024-11-10 01:02:30 +01:00
Merge branch 'master' of https://github.com/ajayyy/SponsorBlockServer
This commit is contained in:
commit
68d9d3cbde
78 changed files with 2473 additions and 260 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -13,8 +13,8 @@ jobs:
|
|||
|
||||
steps:
|
||||
# Initialization
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- run: npm install
|
||||
- name: Run Tests
|
||||
timeout-minutes: 5
|
||||
|
|
4
.github/workflows/eslint.yml
vendored
4
.github/workflows/eslint.yml
vendored
|
@ -13,8 +13,8 @@ jobs:
|
|||
|
||||
steps:
|
||||
# Initialization
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- run: npm install
|
||||
- name: Run Tests
|
||||
timeout-minutes: 5
|
||||
|
|
6
.github/workflows/generate-sqlite-base.yml
vendored
6
.github/workflows/generate-sqlite-base.yml
vendored
|
@ -14,8 +14,8 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- run: npm install
|
||||
- name: Set config
|
||||
run: |
|
||||
|
@ -23,7 +23,7 @@ jobs:
|
|||
- name: Run Server
|
||||
timeout-minutes: 10
|
||||
run: npm start
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: SponsorTimesDB.db
|
||||
path: databases/sponsorTimes.db
|
||||
|
|
8
.github/workflows/postgres-redis-ci.yml
vendored
8
.github/workflows/postgres-redis-ci.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build the docker-compose stack
|
||||
env:
|
||||
PG_USER: ci_db_user
|
||||
|
@ -20,10 +20,12 @@ jobs:
|
|||
run: docker-compose -f docker/docker-compose-ci.yml up -d
|
||||
- name: Check running containers
|
||||
run: docker ps
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
- run: npm install
|
||||
- name: Run Tests
|
||||
env:
|
||||
TEST_POSTGRES: true
|
||||
timeout-minutes: 5
|
||||
run: npm test
|
||||
run: npx nyc --silent npm test
|
||||
- name: Generate coverage report
|
||||
run: npm run cover:report
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -47,4 +47,5 @@ working
|
|||
/dist/
|
||||
|
||||
# nyc coverage output
|
||||
.nyc_output/
|
||||
.nyc_output/
|
||||
coverage/
|
11
.nycrc.json
11
.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/*"
|
||||
]
|
||||
}
|
14
ci.json
14
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
|
||||
}
|
||||
|
|
109
package-lock.json
generated
109
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -45,6 +45,8 @@ import { youtubeApiProxy } from "./routes/youtubeApiProxy";
|
|||
import { getChapterNames } from "./routes/getChapterNames";
|
||||
import { getTopCategoryUsers } from "./routes/getTopCategoryUsers";
|
||||
import { addUserAsTempVIP } from "./routes/addUserAsTempVIP";
|
||||
import { endpoint as getVideoLabels } from "./routes/getVideoLabel";
|
||||
import { getVideoLabelsByHash } from "./routes/getVideoLabelByHash";
|
||||
import { addFeature } from "./routes/addFeature";
|
||||
import { generateTokenRequest } from "./routes/generateToken";
|
||||
import { verifyTokenRequest } from "./routes/verifyToken";
|
||||
|
@ -200,6 +202,11 @@ function setupRoutes(router: Router) {
|
|||
router.get("/api/generateToken/:type", generateTokenRequest);
|
||||
router.get("/api/verifyToken", verifyTokenRequest);
|
||||
|
||||
// labels
|
||||
router.get("/api/videoLabels", getVideoLabels);
|
||||
router.get("/api/videoLabels/:prefix", getVideoLabelsByHash);
|
||||
|
||||
/* 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));
|
||||
|
@ -211,4 +218,4 @@ function setupRoutes(router: Router) {
|
|||
});
|
||||
}
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-misused-promises */
|
||||
/* eslint-enable @typescript-eslint/no-misused-promises */
|
||||
|
|
|
@ -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.",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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(`
|
||||
<h1>
|
||||
|
@ -45,5 +46,7 @@ export async function generateTokenRequest(req: GenerateTokenRequest, res: Respo
|
|||
</h1>
|
||||
`);
|
||||
}
|
||||
} else {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export async function getIsUserVIP(req: Request, res: Response): Promise<Respons
|
|||
hashedUserID: hashedUserID,
|
||||
vip: vipState,
|
||||
});
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
Logger.error(err as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ export async function getLockCategories(req: Request, res: Response): Promise<Re
|
|||
categories,
|
||||
actionTypes
|
||||
});
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */{
|
||||
Logger.error(err as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
|
|
@ -44,14 +44,25 @@ const mergeLocks = (source: DBLock[], actionTypes: ActionType[]): LockResultByHa
|
|||
|
||||
export async function getLockCategoriesByHash(req: Request, res: Response): Promise<Response> {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -32,18 +32,24 @@ export async function getLockReason(req: Request, res: Response): Promise<Respon
|
|||
return res.status(400).send("No videoID provided");
|
||||
}
|
||||
let categories: Category[] = [];
|
||||
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];
|
||||
const possibleCategories = filterActionType(actionTypes);
|
||||
if (!Array.isArray(actionTypes)) {
|
||||
//invalid request
|
||||
return res.status(400).send("actionTypes parameter does not match format requirements");
|
||||
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.status(400).send("actionTypes parameter does not match format requirements");
|
||||
}
|
||||
} catch (error) {
|
||||
return res.status(400).send("Bad parameter: actionTypes (invalid JSON)");
|
||||
}
|
||||
const possibleCategories = filterActionType(actionTypes);
|
||||
|
||||
try {
|
||||
categories = req.query.categories
|
||||
? JSON.parse(req.query.categories as string)
|
||||
|
@ -64,11 +70,6 @@ export async function getLockReason(req: Request, res: Response): Promise<Respon
|
|||
: categories.filter(x =>
|
||||
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<Respon
|
|||
}
|
||||
|
||||
return res.send(results);
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
Logger.error(err as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ export async function getSavedTimeForUser(req: Request, res: Response): Promise<
|
|||
} else {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
Logger.error(`getSavedTimeForUser ${err}`);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
|
|
@ -128,12 +128,7 @@ async function handleGetSegments(req: Request, res: Response): Promise<searchSeg
|
|||
|
||||
const segments = await getSegmentsFromDBByVideoID(videoID, service);
|
||||
|
||||
if (segments === null || segments === undefined) {
|
||||
res.sendStatus(500);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (segments.length === 0) {
|
||||
if (!segments?.length) {
|
||||
res.sendStatus(404);
|
||||
return false;
|
||||
}
|
||||
|
@ -155,6 +150,7 @@ function filterSegments(segments: DBSegment[], filters: Record<string, any>, 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<Response> {
|
|||
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);
|
||||
|
|
|
@ -7,7 +7,7 @@ const isValidSegmentUUID = (str: string): boolean => /^([a-f0-9]{64}|[a-f0-9]{8}
|
|||
async function getSegmentFromDBByUUID(UUID: SegmentUUID): Promise<DBSegment> {
|
||||
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<Response> {
|
|||
//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);
|
||||
|
|
|
@ -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<Response> {
|
|||
//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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -18,7 +18,7 @@ export async function getStatus(req: Request, res: Response): Promise<Response>
|
|||
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<Response>
|
|||
.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<Response>
|
|||
|
||||
const statusValues: Record<string, any> = {
|
||||
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<Response>
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -75,11 +75,6 @@ export async function getTopUsers(req: Request, res: Response): Promise<Response
|
|||
const sortType = parseInt(req.query.sortType as string);
|
||||
const categoryStatsEnabled = req.query.categoryStats;
|
||||
|
||||
if (sortType == undefined) {
|
||||
//invalid request
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
//setup which sort type to use
|
||||
let sortBy = "";
|
||||
if (sortType == 0) {
|
||||
|
|
|
@ -12,7 +12,7 @@ function getFuzzyUserID(userName: string): Promise<{userName: string, userID: Us
|
|||
try {
|
||||
return db.prepare("all", `SELECT "userName", "userID" FROM "userNames" WHERE "userName"
|
||||
LIKE ? ESCAPE '\\' LIMIT 10`, [userName]);
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ function getFuzzyUserID(userName: string): Promise<{userName: string, userID: Us
|
|||
function getExactUserID(userName: string): Promise<{userName: string, userID: UserID }[]> {
|
||||
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<Response>
|
|||
: await getFuzzyUserID(userName);
|
||||
|
||||
if (results === undefined || results === null) {
|
||||
/* istanbul ignore next */
|
||||
return res.sendStatus(500);
|
||||
} else if (results.length === 0) {
|
||||
return res.sendStatus(404);
|
||||
|
|
|
@ -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<number> {
|
|||
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<number> {
|
|||
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<SegmentUUI
|
|||
try {
|
||||
const row = await db.prepare("get", `SELECT "UUID" FROM "sponsorTimes" WHERE "userID" = ? ORDER BY "timeSubmitted" DESC LIMIT 1`, [userID], { useReplica: true });
|
||||
return row?.UUID ?? null;
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ async function dbGetActiveWarningReasonForUser(userID: HashedUserID): Promise<st
|
|||
try {
|
||||
const row = await db.prepare("get", `SELECT reason FROM "warnings" WHERE "userID" = ? AND "enabled" = 1 ORDER BY "issueTime" DESC LIMIT 1`, [userID], { useReplica: true });
|
||||
return row?.reason ?? "";
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
Logger.error(`Couldn't get reason for user ${userID}. returning blank`);
|
||||
return "";
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ async function dbGetBanned(userID: HashedUserID): Promise<boolean> {
|
|||
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<Response> {
|
|||
export async function endpoint(req: Request, res: Response): Promise<Response> {
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ export async function getUsername(req: Request, res: Response): Promise<Response
|
|||
userName: userID,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
Logger.error(err as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
|
167
src/routes/getVideoLabel.ts
Normal file
167
src/routes/getVideoLabel.ts
Normal file
|
@ -0,0 +1,167 @@
|
|||
import { Request, Response } from "express";
|
||||
import { db } from "../databases/databases";
|
||||
import { videoLabelsHashKey, videoLabelsKey } from "../utils/redisKeys";
|
||||
import { SBRecord } from "../types/lib.model";
|
||||
import { DBSegment, Segment, Service, VideoData, VideoID, VideoIDHash } from "../types/segments.model";
|
||||
import { Logger } from "../utils/logger";
|
||||
import { QueryCacher } from "../utils/queryCacher";
|
||||
import { getService } from "../utils/getService";
|
||||
|
||||
function transformDBSegments(segments: DBSegment[]): Segment[] {
|
||||
return segments.map((chosenSegment) => ({
|
||||
category: chosenSegment.category,
|
||||
actionType: chosenSegment.actionType,
|
||||
segment: [chosenSegment.startTime, chosenSegment.endTime],
|
||||
UUID: chosenSegment.UUID,
|
||||
locked: chosenSegment.locked,
|
||||
votes: chosenSegment.votes,
|
||||
videoDuration: chosenSegment.videoDuration,
|
||||
userID: chosenSegment.userID,
|
||||
description: chosenSegment.description
|
||||
}));
|
||||
}
|
||||
|
||||
async function getLabelsByVideoID(videoID: VideoID, service: Service): Promise<Segment[]> {
|
||||
try {
|
||||
const segments: DBSegment[] = await getSegmentsFromDBByVideoID(videoID, service);
|
||||
return chooseSegment(segments);
|
||||
} catch (err) {
|
||||
if (err) {
|
||||
Logger.error(err as string);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getLabelsByHash(hashedVideoIDPrefix: VideoIDHash, service: Service): Promise<SBRecord<VideoID, VideoData>> {
|
||||
const segments: SBRecord<VideoID, VideoData> = {};
|
||||
|
||||
try {
|
||||
type SegmentWithHashPerVideoID = SBRecord<VideoID, { hash: VideoIDHash, segments: DBSegment[] }>;
|
||||
|
||||
const segmentPerVideoID: SegmentWithHashPerVideoID = (await getSegmentsFromDBByHash(hashedVideoIDPrefix, service))
|
||||
.reduce((acc: SegmentWithHashPerVideoID, segment: DBSegment) => {
|
||||
acc[segment.videoID] = acc[segment.videoID] || {
|
||||
hash: segment.hashedVideoID,
|
||||
segments: []
|
||||
};
|
||||
|
||||
acc[segment.videoID].segments ??= [];
|
||||
acc[segment.videoID].segments.push(segment);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
for (const [videoID, videoData] of Object.entries(segmentPerVideoID)) {
|
||||
const data: VideoData = {
|
||||
hash: videoData.hash,
|
||||
segments: chooseSegment(videoData.segments),
|
||||
};
|
||||
|
||||
if (data.segments.length > 0) {
|
||||
segments[videoID] = data;
|
||||
}
|
||||
}
|
||||
|
||||
return segments;
|
||||
} catch (err) {
|
||||
Logger.error(err as string);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service: Service): Promise<DBSegment[]> {
|
||||
const fetchFromDB = () => db
|
||||
.prepare(
|
||||
"all",
|
||||
`SELECT "startTime", "endTime", "videoID", "votes", "locked", "UUID", "userID", "category", "actionType", "hashedVideoID", "description" FROM "sponsorTimes"
|
||||
WHERE "hashedVideoID" LIKE ? AND "service" = ? AND "actionType" = 'full' AND "hidden" = 0 AND "shadowHidden" = 0`,
|
||||
[`${hashedVideoIDPrefix}%`, service]
|
||||
) as Promise<DBSegment[]>;
|
||||
|
||||
if (hashedVideoIDPrefix.length === 4) {
|
||||
return await QueryCacher.get(fetchFromDB, videoLabelsHashKey(hashedVideoIDPrefix, service));
|
||||
}
|
||||
|
||||
return await fetchFromDB();
|
||||
}
|
||||
|
||||
async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): Promise<DBSegment[]> {
|
||||
const fetchFromDB = () => db
|
||||
.prepare(
|
||||
"all",
|
||||
`SELECT "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "description" FROM "sponsorTimes"
|
||||
WHERE "videoID" = ? AND "service" = ? AND "actionType" = 'full' AND "hidden" = 0 AND "shadowHidden" = 0`,
|
||||
[videoID, service]
|
||||
) as Promise<DBSegment[]>;
|
||||
|
||||
return await QueryCacher.get(fetchFromDB, videoLabelsKey(videoID, service));
|
||||
}
|
||||
|
||||
function chooseSegment<T extends DBSegment>(choices: T[]): Segment[] {
|
||||
// filter out -2 segments
|
||||
choices = choices.filter((segment) => segment.votes > -2);
|
||||
const results = [];
|
||||
// trivial decisions
|
||||
if (choices.length === 0) {
|
||||
return [];
|
||||
} else if (choices.length === 1) {
|
||||
return transformDBSegments(choices);
|
||||
}
|
||||
// if locked, only choose from locked
|
||||
const locked = choices.filter((segment) => segment.locked);
|
||||
if (locked.length > 0) {
|
||||
choices = locked;
|
||||
}
|
||||
//no need to filter, just one label
|
||||
if (choices.length === 1) {
|
||||
return transformDBSegments(choices);
|
||||
}
|
||||
// sponsor > exclusive > selfpromo
|
||||
const findCategory = (category: string) => choices.find((segment) => segment.category === category);
|
||||
|
||||
const categoryResult = findCategory("sponsor") ?? findCategory("exclusive_access") ?? findCategory("selfpromo");
|
||||
if (categoryResult) results.push(categoryResult);
|
||||
|
||||
return transformDBSegments(results);
|
||||
}
|
||||
|
||||
async function handleGetLabel(req: Request, res: Response): Promise<Segment[] | false> {
|
||||
const videoID = req.query.videoID as VideoID;
|
||||
if (!videoID) {
|
||||
res.status(400).send("videoID not specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
const service = getService(req.query.service, req.body.service);
|
||||
const segments = await getLabelsByVideoID(videoID, service);
|
||||
|
||||
if (!segments || segments.length === 0) {
|
||||
res.sendStatus(404);
|
||||
return false;
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
async function endpoint(req: Request, res: Response): Promise<Response> {
|
||||
try {
|
||||
const segments = await handleGetLabel(req, res);
|
||||
|
||||
// If false, res.send has already been called
|
||||
if (segments) {
|
||||
//send result
|
||||
return res.send(segments);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
return res.status(400).send("Categories parameter does not match format requirements.");
|
||||
} else return res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getLabelsByVideoID,
|
||||
getLabelsByHash,
|
||||
endpoint
|
||||
};
|
27
src/routes/getVideoLabelByHash.ts
Normal file
27
src/routes/getVideoLabelByHash.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { hashPrefixTester } from "../utils/hashPrefixTester";
|
||||
import { getLabelsByHash } from "./getVideoLabel";
|
||||
import { Request, Response } from "express";
|
||||
import { VideoIDHash, Service } from "../types/segments.model";
|
||||
import { getService } from "../utils/getService";
|
||||
|
||||
export async function getVideoLabelsByHash(req: Request, res: Response): Promise<Response> {
|
||||
let hashPrefix = req.params.prefix as VideoIDHash;
|
||||
if (!req.params.prefix || !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;
|
||||
|
||||
const service: Service = getService(req.query.service, req.body.service);
|
||||
|
||||
// Get all video id's that match hash prefix
|
||||
const segments = await getLabelsByHash(hashPrefix, service);
|
||||
|
||||
if (!segments) return res.status(404).json([]);
|
||||
|
||||
const output = Object.entries(segments).map(([videoID, data]) => ({
|
||||
videoID,
|
||||
hash: data.hash,
|
||||
segments: data.segments,
|
||||
}));
|
||||
return res.status(output.length === 0 ? 404 : 200).json(output);
|
||||
}
|
|
@ -25,7 +25,7 @@ export async function getViewsForUser(req: Request, res: Response): Promise<Resp
|
|||
} else {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
Logger.error(err as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export async function postClearCache(req: Request, res: Response): Promise<Respo
|
|||
return res.status(200).json({
|
||||
message: `Cache cleared on video ${videoID}`
|
||||
});
|
||||
} catch(err) {
|
||||
} catch(err) /* istanbul ignore next */ {
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ export async function postLockCategories(req: Request, res: Response): Promise<s
|
|||
|
||||
if (!userIsVIP) {
|
||||
res.status(403).json({
|
||||
message: "Must be a VIP to mark videos.",
|
||||
message: "Must be a VIP to lock videos.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ export async function postLockCategories(req: Request, res: Response): Promise<s
|
|||
for (const lock of locksToApply) {
|
||||
try {
|
||||
await db.prepare("run", `INSERT INTO "lockCategories" ("videoID", "userID", "actionType", "category", "hashedVideoID", "reason", "service") VALUES(?, ?, ?, ?, ?, ?, ?)`, [videoID, userID, lock.actionType, lock.category, hashedVideoID, reason, service]);
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
Logger.error(`Error submitting 'lockCategories' marker for category '${lock.category}' and actionType '${lock.actionType}' for video '${videoID}' (${service})`);
|
||||
Logger.error(err as string);
|
||||
res.status(500).json({
|
||||
|
@ -82,7 +82,7 @@ export async function postLockCategories(req: Request, res: Response): Promise<s
|
|||
await db.prepare("run",
|
||||
'UPDATE "lockCategories" SET "reason" = ?, "userID" = ? WHERE "videoID" = ? AND "actionType" = ? AND "category" = ? AND "service" = ?',
|
||||
[reason, userID, videoID, lock.actionType, lock.category, service]);
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
Logger.error(`Error submitting 'lockCategories' marker for category '${lock.category}' and actionType '${lock.actionType}' for video '${videoID}' (${service})`);
|
||||
Logger.error(err as string);
|
||||
res.status(500).json({
|
||||
|
|
|
@ -37,7 +37,7 @@ export async function postPurgeAllSegments(req: Request, res: Response): Promise
|
|||
service
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
Logger.error(err as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ export async function postSegmentShift(req: Request, res: Response): Promise<Res
|
|||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
Logger.error(err as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ export async function setUsername(req: Request, res: Response): Promise<Response
|
|||
return res.sendStatus(200);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
catch (error) /* istanbul ignore next */ {
|
||||
Logger.error(error as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ export async function setUsername(req: Request, res: Response): Promise<Response
|
|||
await logUserNameChange(userID, userName, oldUserName, adminUserIDInput !== undefined);
|
||||
|
||||
return res.sendStatus(200);
|
||||
} catch (err) {
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
Logger.error(err as string);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { config } from "../config";
|
|||
import { privateDB } from "../databases/databases";
|
||||
import { Logger } from "../utils/logger";
|
||||
import { getPatreonIdentity, PatronStatus, refreshToken, TokenType } from "../utils/tokenUtils";
|
||||
import FormData from "form-data";
|
||||
|
||||
interface VerifyTokenRequest extends Request {
|
||||
query: {
|
||||
|
@ -12,14 +11,16 @@ interface VerifyTokenRequest extends Request {
|
|||
}
|
||||
}
|
||||
|
||||
export const validatelicenseKeyRegex = (token: string) =>
|
||||
new RegExp(/[A-Za-z0-9]{40}|[A-Za-z0-9-]{35}/).test(token);
|
||||
|
||||
export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response): Promise<Response> {
|
||||
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<boolean> {
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { HashedUserID, UserID } from "./user.model";
|
|||
export type SegmentUUID = string & { __segmentUUIDBrand: unknown };
|
||||
export type VideoID = string & { __videoIDBrand: unknown };
|
||||
export type VideoDuration = number & { __videoDurationBrand: unknown };
|
||||
export type Category = ("sponsor" | "selfpromo" | "interaction" | "intro" | "outro" | "preview" | "music_offtopic" | "filler" | "poi_highlight" | "chapter") & { __categoryBrand: unknown };
|
||||
export type Category = ("sponsor" | "selfpromo" | "interaction" | "intro" | "outro" | "preview" | "music_offtopic" | "poi_highlight" | "chapter" | "filler" | "exclusive_access") & { __categoryBrand: unknown };
|
||||
export type VideoIDHash = VideoID & HashedValue;
|
||||
export type IPAddress = string & { __ipAddressBrand: unknown };
|
||||
export type HashedIP = IPAddress & HashedValue;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ async function getFromITube (videoID: string): Promise<innerTubeVideoDetails> {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import redis from "../utils/redis";
|
||||
import { Logger } from "../utils/logger";
|
||||
import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, skipSegmentGroupsKey, userFeatureKey } from "./redisKeys";
|
||||
import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, skipSegmentGroupsKey, userFeatureKey, videoLabelsKey, videoLabelsHashKey } from "./redisKeys";
|
||||
import { Service, VideoID, VideoIDHash } from "../types/segments.model";
|
||||
import { Feature, HashedUserID, UserID } from "../types/user.model";
|
||||
import { config } from "../config";
|
||||
|
@ -81,6 +81,8 @@ function clearSegmentCache(videoInfo: { videoID: VideoID; hashedVideoID: VideoID
|
|||
redis.del(skipSegmentsKey(videoInfo.videoID, videoInfo.service)).catch((err) => Logger.error(err));
|
||||
redis.del(skipSegmentGroupsKey(videoInfo.videoID, videoInfo.service)).catch((err) => Logger.error(err));
|
||||
redis.del(skipSegmentsHashKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err));
|
||||
redis.del(videoLabelsKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err));
|
||||
redis.del(videoLabelsHashKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err));
|
||||
if (videoInfo.userID) redis.del(reputationKey(videoInfo.userID)).catch((err) => Logger.error(err));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,16 @@ export function shaHashKey(singleIter: HashedValue): string {
|
|||
export const tempVIPKey = (userID: HashedUserID): string =>
|
||||
`vip.temp.${userID}`;
|
||||
|
||||
export const videoLabelsKey = (videoID: VideoID, service: Service): string =>
|
||||
`labels.v1.${service}.videoID.${videoID}`;
|
||||
|
||||
export function videoLabelsHashKey(hashedVideoIDPrefix: VideoIDHash, service: Service): string {
|
||||
hashedVideoIDPrefix = hashedVideoIDPrefix.substring(0, 4) as VideoIDHash;
|
||||
if (hashedVideoIDPrefix.length !== 4) Logger.warn(`Redis skip segment hash-prefix key is not length 4! ${hashedVideoIDPrefix}`);
|
||||
|
||||
return `labels.v1.${service}.${hashedVideoIDPrefix}`;
|
||||
}
|
||||
|
||||
export function userFeatureKey (userID: HashedUserID, feature: Feature): string {
|
||||
return `user.${userID}.feature.${feature}`;
|
||||
}
|
|
@ -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<PatreonId
|
|||
if (identityRequest.status === 200) {
|
||||
return identityRequest.data;
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e) /* istanbul ignore next */ {
|
||||
Logger.error(`identity request: ${e}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
14
test.json
14
test.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,
|
||||
"db": ":memory:",
|
||||
"privateDB": ":memory:",
|
||||
|
@ -58,5 +59,10 @@
|
|||
"statusCode": 200
|
||||
}
|
||||
},
|
||||
"patreon": {
|
||||
"clientId": "testClientID",
|
||||
"clientSecret": "testClientSecret",
|
||||
"redirectUri": "http://127.0.0.1/fake/callback"
|
||||
},
|
||||
"minReputationToSubmitFiller": -1
|
||||
}
|
||||
|
|
141
test/cases/addUserAsVIP.ts
Normal file
141
test/cases/addUserAsVIP.ts
Normal file
|
@ -0,0 +1,141 @@
|
|||
import { getHash } from "../../src/utils/getHash";
|
||||
import { HashedUserID } from "../../src/types/user.model";
|
||||
import { client } from "../utils/httpClient";
|
||||
import { db } from "../../src/databases/databases";
|
||||
import assert from "assert";
|
||||
|
||||
// helpers
|
||||
const checkUserVIP = (publicID: string) => 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));
|
||||
});
|
||||
});
|
189
test/cases/generateVerifyToken.ts
Normal file
189
test/cases/generateVerifyToken.ts
Normal file
|
@ -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));
|
||||
});
|
||||
});
|
27
test/cases/getDaysSavedFormatted.ts
Normal file
27
test/cases/getDaysSavedFormatted.ts
Normal file
|
@ -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();
|
||||
});
|
||||
});
|
109
test/cases/getIP.ts
Normal file
109
test/cases/getIP.ts
Normal file
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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: [
|
||||
"sponsor"
|
||||
"preview"
|
||||
],
|
||||
reason: "fake2-notshown"
|
||||
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: [
|
||||
"preview"
|
||||
],
|
||||
reason: "2-reason"
|
||||
}];
|
||||
assert.deepStrictEqual(res.data, expected);
|
||||
done();
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 => {
|
||||
|
|
48
test/cases/getSearchSegments4xx.ts
Normal file
48
test/cases/getSearchSegments4xx.ts
Normal file
|
@ -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));
|
||||
});
|
||||
});
|
|
@ -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<any>).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));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 => {
|
||||
|
|
17
test/cases/getTotalStats.ts
Normal file
17
test/cases/getTotalStats.ts
Normal file
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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 => {
|
||||
|
|
76
test/cases/getUserInfoFree.ts
Normal file
76
test/cases/getUserInfoFree.ts
Normal file
|
@ -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));
|
||||
});
|
||||
});
|
53
test/cases/getUsername.ts
Normal file
53
test/cases/getUsername.ts
Normal file
|
@ -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));
|
||||
});
|
||||
});
|
184
test/cases/getVideoLabelByHash.ts
Normal file
184
test/cases/getVideoLabelByHash.ts
Normal file
|
@ -0,0 +1,184 @@
|
|||
import { db } from "../../src/databases/databases";
|
||||
import assert from "assert";
|
||||
import { client } from "../utils/httpClient";
|
||||
import { getHash } from "../../src/utils/getHash";
|
||||
|
||||
describe("getVideoLabelHash", () => {
|
||||
const endpoint = "/api/videoLabels";
|
||||
before(async () => {
|
||||
const query = 'INSERT INTO "sponsorTimes" ("videoID", "hashedVideoID", "votes", "locked", "UUID", "userID", "timeSubmitted", "category", "actionType", "hidden", "shadowHidden", "startTime", "endTime", "views") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0)';
|
||||
await db.prepare("run", query, ["getLabelHashSponsor" , getHash("getLabelHashSponsor", 1) , 2, 0, "labelhash01", "labeluser", 0, "sponsor", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelHashEA" , getHash("getLabelHashEA", 1) , 2, 0, "labelhash02", "labeluser", 0, "exclusive_access", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelHashSelfpromo" , getHash("getLabelHashSelfpromo", 1) , 2, 0, "labelhash03", "labeluser", 0, "selfpromo", "full", 0, 0]);
|
||||
// priority override
|
||||
await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash04", "labeluser", 0, "sponsor", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash05", "labeluser", 0, "exclusive_access", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash06", "labeluser", 0, "selfpromo", "full", 0, 0]);
|
||||
// locked only
|
||||
await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 0, "labelhash07", "labeluser", 0, "sponsor", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 0, "labelhash08", "labeluser", 0, "exclusive_access", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 1, "labelhash09", "labeluser", 0, "selfpromo", "full", 0, 0]);
|
||||
// hidden segments
|
||||
await db.prepare("run", query, ["getLabelHashDownvote" , getHash("getLabelHashDownvote", 1) , -2, 0, "labelhash10", "labeluser", 0, "selfpromo", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelHashHidden" , getHash("getLabelHashHidden", 1) , 2, 0, "labelhash11", "labeluser", 0, "selfpromo", "full", 1, 0]);
|
||||
await db.prepare("run", query, ["getLabelHashShHidden" , getHash("getLabelHashShHidden", 1) , 2, 0, "labelhash12", "labeluser", 0, "selfpromo", "full", 0, 1]);
|
||||
// priority override2
|
||||
await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , -2, 0, "labelhash13", "labeluser", 0, "sponsor", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , 2, 0, "labelhash14", "labeluser", 0, "exclusive_access", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , 2, 0, "labelhash15", "labeluser", 0, "selfpromo", "full", 0, 0]);
|
||||
|
||||
return;
|
||||
});
|
||||
|
||||
function validateLabel(data: any, videoID: string) {
|
||||
assert.strictEqual(data[0].videoID, videoID);
|
||||
assert.strictEqual(data[0].segments.length, 1);
|
||||
assert.strictEqual(data[0].segments[0].segment[0], 0);
|
||||
assert.strictEqual(data[0].segments[0].segment[1], 0);
|
||||
assert.strictEqual(data[0].segments[0].actionType, "full");
|
||||
assert.strictEqual(data[0].segments[0].userID, "labeluser");
|
||||
}
|
||||
|
||||
const get = (videoID: string) => client.get(`${endpoint}/${getHash(videoID, 1).substring(0, 4)}`);
|
||||
|
||||
it("Should be able to get sponsor only label", (done) => {
|
||||
const videoID = "getLabelHashSponsor";
|
||||
get(videoID)
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data, videoID);
|
||||
const result = data[0].segments[0];
|
||||
assert.strictEqual(result.category, "sponsor");
|
||||
assert.strictEqual(result.UUID, "labelhash01");
|
||||
assert.strictEqual(result.locked, 0);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should be able to get exclusive access only label", (done) => {
|
||||
const videoID = "getLabelHashEA";
|
||||
get(videoID)
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data, videoID);
|
||||
const result = data[0].segments[0];
|
||||
assert.strictEqual(result.category, "exclusive_access");
|
||||
assert.strictEqual(result.UUID, "labelhash02");
|
||||
assert.strictEqual(result.locked, 0);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should be able to get selfpromo only label", (done) => {
|
||||
const videoID = "getLabelHashSelfpromo";
|
||||
get(videoID)
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data, videoID);
|
||||
const result = data[0].segments[0];
|
||||
assert.strictEqual(result.category, "selfpromo");
|
||||
assert.strictEqual(result.UUID, "labelhash03");
|
||||
assert.strictEqual(result.locked, 0);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should get only sponsor if multiple segments exist", (done) => {
|
||||
const videoID = "getLabelHashPriority";
|
||||
get(videoID)
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data, videoID);
|
||||
const result = data[0].segments[0];
|
||||
assert.strictEqual(result.category, "sponsor");
|
||||
assert.strictEqual(result.UUID, "labelhash04");
|
||||
assert.strictEqual(result.locked, 0);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should override priority if locked", (done) => {
|
||||
const videoID = "getLabelHashLocked";
|
||||
get(videoID)
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data, videoID);
|
||||
const result = data[0].segments[0];
|
||||
assert.strictEqual(result.category, "selfpromo");
|
||||
assert.strictEqual(result.UUID, "labelhash09");
|
||||
assert.strictEqual(result.locked, 1);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should get highest priority category", (done) => {
|
||||
const videoID = "getLabelHashPriority2";
|
||||
get(videoID)
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data, videoID);
|
||||
const result = data[0].segments[0];
|
||||
assert.strictEqual(result.category, "exclusive_access");
|
||||
assert.strictEqual(result.UUID, "labelhash14");
|
||||
assert.strictEqual(result.locked, 0);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should return 404 if all submissions are downvoted", (done) => {
|
||||
get("getLabelHashDownvote")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 404);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should return 404 if all submissions are hidden", (done) => {
|
||||
get("getLabelHashHidden")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 404);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should return 404 if all submissions are shadowhidden", (done) => {
|
||||
get("getLabelHashShHidden")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 404);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should return 404 if no segment found", (done) => {
|
||||
get("notarealvideo")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 404);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should get 400 if no videoID passed in", (done) => {
|
||||
client.get(endpoint)
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 400);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
});
|
170
test/cases/getVideoLabels.ts
Normal file
170
test/cases/getVideoLabels.ts
Normal file
|
@ -0,0 +1,170 @@
|
|||
import { db } from "../../src/databases/databases";
|
||||
import assert from "assert";
|
||||
import { client } from "../utils/httpClient";
|
||||
|
||||
describe("getVideoLabels", () => {
|
||||
const endpoint = "/api/videoLabels";
|
||||
before(async () => {
|
||||
const query = 'INSERT INTO "sponsorTimes" ("videoID", "votes", "locked", "UUID", "userID", "timeSubmitted", "category", "actionType", "hidden", "shadowHidden", "startTime", "endTime", "views") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0)';
|
||||
await db.prepare("run", query, ["getLabelSponsor" , 2, 0, "label01", "labeluser", 0, "sponsor", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelEA" , 2, 0, "label02", "labeluser", 0, "exclusive_access", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelSelfpromo" , 2, 0, "label03", "labeluser", 0, "selfpromo", "full", 0, 0]);
|
||||
// priority override
|
||||
await db.prepare("run", query, ["getLabelPriority" , 2, 0, "label04", "labeluser", 0, "sponsor", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelPriority" , 2, 0, "label05", "labeluser", 0, "exclusive_access", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelPriority" , 2, 0, "label06", "labeluser", 0, "selfpromo", "full", 0, 0]);
|
||||
// locked only
|
||||
await db.prepare("run", query, ["getLabelLocked" , 2, 0, "label07", "labeluser", 0, "sponsor", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelLocked" , 2, 0, "label08", "labeluser", 0, "exclusive_access", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelLocked" , 2, 1, "label09", "labeluser", 0, "selfpromo", "full", 0, 0]);
|
||||
// hidden segments
|
||||
await db.prepare("run", query, ["getLabelDownvote" ,-2, 0, "label10", "labeluser", 0, "selfpromo", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelHidden" ,2, 0, "label11", "labeluser", 0, "selfpromo", "full", 1, 0]);
|
||||
await db.prepare("run", query, ["getLabelShadowHidden",2, 0, "label12", "labeluser", 0, "selfpromo", "full", 0, 1]);
|
||||
// priority override2
|
||||
await db.prepare("run", query, ["getLabelPriority2" , -2, 0, "label13", "labeluser", 0, "sponsor", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelPriority2" , 2, 0, "label14", "labeluser", 0, "exclusive_access", "full", 0, 0]);
|
||||
await db.prepare("run", query, ["getLabelPriority2" , 2, 0, "label15", "labeluser", 0, "selfpromo", "full", 0, 0]);
|
||||
|
||||
return;
|
||||
});
|
||||
|
||||
function validateLabel(result: any) {
|
||||
assert.strictEqual(result.length, 1);
|
||||
assert.strictEqual(result[0].segment[0], 0);
|
||||
assert.strictEqual(result[0].segment[1], 0);
|
||||
assert.strictEqual(result[0].actionType, "full");
|
||||
assert.strictEqual(result[0].userID, "labeluser");
|
||||
}
|
||||
|
||||
const get = (videoID: string) => client.get(endpoint, { params: { videoID } });
|
||||
|
||||
it("Should be able to get sponsor only label", (done) => {
|
||||
get("getLabelSponsor")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data);
|
||||
assert.strictEqual(data[0].category, "sponsor");
|
||||
assert.strictEqual(data[0].UUID, "label01");
|
||||
assert.strictEqual(data[0].locked, 0);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should be able to get exclusive access only label", (done) => {
|
||||
get("getLabelEA")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data);
|
||||
assert.strictEqual(data[0].category, "exclusive_access");
|
||||
assert.strictEqual(data[0].UUID, "label02");
|
||||
assert.strictEqual(data[0].locked, 0);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should be able to get selfpromo only label", (done) => {
|
||||
get("getLabelSelfpromo")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data);
|
||||
assert.strictEqual(data[0].category, "selfpromo");
|
||||
assert.strictEqual(data[0].UUID, "label03");
|
||||
assert.strictEqual(data[0].locked, 0);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should get only sponsor if multiple segments exist", (done) => {
|
||||
get("getLabelPriority")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data);
|
||||
assert.strictEqual(data[0].category, "sponsor");
|
||||
assert.strictEqual(data[0].UUID, "label04");
|
||||
assert.strictEqual(data[0].locked, 0);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should override priority if locked", (done) => {
|
||||
get("getLabelLocked")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data);
|
||||
assert.strictEqual(data[0].category, "selfpromo");
|
||||
assert.strictEqual(data[0].UUID, "label09");
|
||||
assert.strictEqual(data[0].locked, 1);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should get highest priority category", (done) => {
|
||||
get("getLabelPriority2")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 200);
|
||||
const data = res.data;
|
||||
validateLabel(data);
|
||||
assert.strictEqual(data[0].category, "exclusive_access");
|
||||
assert.strictEqual(data[0].UUID, "label14");
|
||||
assert.strictEqual(data[0].locked, 0);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should return 404 if all submissions are downvoted", (done) => {
|
||||
get("getLabelDownvote")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 404);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should return 404 if all submissions are hidden", (done) => {
|
||||
get("getLabelHidden")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 404);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should return 404 if all submissions are shadowhidden", (done) => {
|
||||
get("getLabelShadowHidden")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 404);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should return 404 if no segment found", (done) => {
|
||||
get("notarealvideo")
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 404);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
|
||||
it("Should get 400 if no videoID passed in", (done) => {
|
||||
client.get(endpoint)
|
||||
.then(res => {
|
||||
assert.strictEqual(res.status, 400);
|
||||
done();
|
||||
})
|
||||
.catch(err => done(err));
|
||||
});
|
||||
});
|
62
test/cases/getViewsForUser.ts
Normal file
62
test/cases/getViewsForUser.ts
Normal file
|
@ -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));
|
||||
});
|
||||
});
|
252
test/cases/lockCategoriesHttp.ts
Normal file
252
test/cases/lockCategoriesHttp.ts
Normal file
|
@ -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<LockCategory[]> => 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));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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 = {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
})
|
||||
|
|
48
test/cases/shadowBanUser4xx.ts
Normal file
48
test/cases/shadowBanUser4xx.ts
Normal file
|
@ -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<string, string>) => 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));
|
||||
});
|
||||
});
|
|
@ -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
|
||||
}));
|
||||
});
|
||||
});
|
50
test/cases/tokenUtils.ts
Normal file
50
test/cases/tokenUtils.ts
Normal file
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
11
test/mocks/UserCounter.ts
Normal file
11
test/mocks/UserCounter.ts
Normal file
|
@ -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
|
||||
});
|
||||
});
|
22
test/mocks/gumroadMock.ts
Normal file
22
test/mocks/gumroadMock.ts
Normal file
|
@ -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()}`;
|
33
test/mocks/mockExpressRequest.ts
Normal file
33
test/mocks/mockExpressRequest.ts
Normal file
|
@ -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
|
||||
});
|
59
test/mocks/patreonMock.ts
Normal file
59
test/mocks/patreonMock.ts
Normal file
|
@ -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,
|
||||
};
|
|
@ -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
|
Loading…
Reference in a new issue