From 9807d3e9c76a9667e0871ffd66b98a2f8a60e57f Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Mon, 6 Apr 2020 20:05:26 -0400 Subject: [PATCH] Merge --- databases/_sponsorTimes.db.sql | 2 +- package.json | 2 + src/routes/getSkipSegment.js | 273 ++++++++++++++++++++++++++++ src/routes/postSkipSegments.js | 68 ++++++- src/utils/youtubeAPI.js | 20 +- test/cases/getSavedTimeForUser.js | 6 +- test/cases/getSponsorTime.js | 25 +-- test/cases/oldSubmitSponsorTimes.js | 32 ++-- test/cases/postSkipSegments.js | 180 ++++++++++-------- test/databases/_private.db.sql | 4 - test/databases/_sponsorTimes.db.sql | 5 +- test/youtubeMock.js | 38 ++++ 12 files changed, 529 insertions(+), 126 deletions(-) create mode 100644 src/routes/getSkipSegment.js create mode 100644 test/youtubeMock.js diff --git a/databases/_sponsorTimes.db.sql b/databases/_sponsorTimes.db.sql index 1514e0e..dfca8bf 100644 --- a/databases/_sponsorTimes.db.sql +++ b/databases/_sponsorTimes.db.sql @@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS "sponsorTimes" ( "userID" TEXT NOT NULL, "timeSubmitted" INTEGER NOT NULL, "views" INTEGER NOT NULL, - "category" TEXT NOT NULL; + "category" TEXT NOT NULL, "shadowHidden" INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS "userNames" ( diff --git a/package.json b/package.json index e7408a0..e162a6b 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "test": "node test.js", "dev": "nodemon -x \"(npm test || echo test failed) && npm start\"", + "dev:bash": "nodemon -x 'npm test ; npm start'", "start": "node index.js" }, "author": "Ajay Ramachandran", @@ -14,6 +15,7 @@ "better-sqlite3": "^5.4.3", "express": "^4.17.1", "http": "0.0.0", + "iso8601-duration": "^1.2.0", "uuid": "^3.3.2", "youtube-api": "^2.0.10" }, diff --git a/src/routes/getSkipSegment.js b/src/routes/getSkipSegment.js new file mode 100644 index 0000000..8918349 --- /dev/null +++ b/src/routes/getSkipSegment.js @@ -0,0 +1,273 @@ +var fs = require('fs'); +var config = require('../config.js'); + +var databases = require('../databases/databases.js'); +var db = databases.db; +var privateDB = databases.privateDB; + +var getHash = require('../utils/getHash.js'); +var getIP = require('../utils/getIP.js'); + + +//gets the getWeightedRandomChoice for each group in an array of groups +function getWeightedRandomChoiceForArray(choiceGroups, weights) { + let finalChoices = []; + //the indexes either chosen to be added to final indexes or chosen not to be added + let choicesDealtWith = []; + //for each choice group, what are the sums of the weights + let weightSums = []; + + for (let i = 0; i < choiceGroups.length; i++) { + //find weight sums for this group + weightSums.push(0); + for (let j = 0; j < choiceGroups[i].length; j++) { + //only if it is a positive vote, otherwise it is probably just a sponsor time with slightly wrong time + if (weights[choiceGroups[i][j]] > 0) { + weightSums[weightSums.length - 1] += weights[choiceGroups[i][j]]; + } + } + + //create a random choice for this group + let randomChoice = getWeightedRandomChoice(choiceGroups[i], weights, 1) + finalChoices.push(randomChoice.finalChoices); + + for (let j = 0; j < randomChoice.choicesDealtWith.length; j++) { + choicesDealtWith.push(randomChoice.choicesDealtWith[j]) + } + } + + return { + finalChoices: finalChoices, + choicesDealtWith: choicesDealtWith, + weightSums: weightSums + }; +} + +//gets a weighted random choice from the indexes array based on the weights. +//amountOfChoices speicifies the amount of choices to return, 1 or more. +//choices are unique +function getWeightedRandomChoice(choices, weights, amountOfChoices) { + if (amountOfChoices > choices.length) { + //not possible, since all choices must be unique + return null; + } + + let finalChoices = []; + let choicesDealtWith = []; + + let sqrtWeightsList = []; + //the total of all the weights run through the cutom sqrt function + let totalSqrtWeights = 0; + for (let j = 0; j < choices.length; j++) { + //multiplying by 10 makes around 13 votes the point where it the votes start not mattering as much (10 + 3) + //The 3 makes -2 the minimum votes before being ignored completely + //https://www.desmos.com/calculator/ljftxolg9j + //this can be changed if this system increases in popularity. + let sqrtVote = Math.sqrt((weights[choices[j]] + 3) * 10); + sqrtWeightsList.push(sqrtVote) + totalSqrtWeights += sqrtVote; + + //this index has now been deat with + choicesDealtWith.push(choices[j]); + } + + //iterate and find amountOfChoices choices + let randomNumber = Math.random(); + + //this array will keep adding to this variable each time one sqrt vote has been dealt with + //this is the sum of all the sqrtVotes under this index + let currentVoteNumber = 0; + for (let j = 0; j < sqrtWeightsList.length; j++) { + if (randomNumber > currentVoteNumber / totalSqrtWeights && randomNumber < (currentVoteNumber + sqrtWeightsList[j]) / totalSqrtWeights) { + //this one was randomly generated + finalChoices.push(choices[j]); + //remove that from original array, for next recursion pass if it happens + choices.splice(j, 1); + break; + } + + //add on to the count + currentVoteNumber += sqrtWeightsList[j]; + } + + //add on the other choices as well using recursion + if (amountOfChoices > 1) { + let otherChoices = getWeightedRandomChoice(choices, weights, amountOfChoices - 1).finalChoices; + //add all these choices to the finalChoices array being returned + for (let i = 0; i < otherChoices.length; i++) { + finalChoices.push(otherChoices[i]); + } + } + + return { + finalChoices: finalChoices, + choicesDealtWith: choicesDealtWith + }; +} + + +//This function will find sponsor times that are contained inside of eachother, called similar sponsor times +//Only one similar time will be returned, randomly generated based on the sqrt of votes. +//This allows new less voted items to still sometimes appear to give them a chance at getting votes. +//Sponsor times with less than -1 votes are already ignored before this function is called +function getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs) { + //list of sponsors that are contained inside eachother + let similarSponsors = []; + + for (let i = 0; i < sponsorTimes.length; i++) { + //see if the start time is located between the start and end time of the other sponsor time. + for (let j = i + 1; j < sponsorTimes.length; j++) { + if (sponsorTimes[j][0] >= sponsorTimes[i][0] && sponsorTimes[j][0] <= sponsorTimes[i][1]) { + //sponsor j is contained in sponsor i + similarSponsors.push([i, j]); + } + } + } + + let similarSponsorsGroups = []; + //once they have been added to a group, they don't need to be dealt with anymore + let dealtWithSimilarSponsors = []; + + //create lists of all the similar groups (if 1 and 2 are similar, and 2 and 3 are similar, the group is 1, 2, 3) + for (let i = 0; i < similarSponsors.length; i++) { + if (dealtWithSimilarSponsors.includes(i)) { + //dealt with already + continue; + } + + //this is the group of indexes that are similar + let group = similarSponsors[i]; + for (let j = 0; j < similarSponsors.length; j++) { + if (group.includes(similarSponsors[j][0]) || group.includes(similarSponsors[j][1])) { + //this is a similar group + group.push(similarSponsors[j][0]); + group.push(similarSponsors[j][1]); + dealtWithSimilarSponsors.push(j); + } + } + similarSponsorsGroups.push(group); + } + + //remove duplicate indexes in group arrays + for (let i = 0; i < similarSponsorsGroups.length; i++) { + uniqueArray = similarSponsorsGroups[i].filter(function(item, pos, self) { + return self.indexOf(item) == pos; + }); + + similarSponsorsGroups[i] = uniqueArray; + } + + let weightedRandomIndexes = getWeightedRandomChoiceForArray(similarSponsorsGroups, votes); + + let finalSponsorTimeIndexes = weightedRandomIndexes.finalChoices; + //the sponsor times either chosen to be added to finalSponsorTimeIndexes or chosen not to be added + let finalSponsorTimeIndexesDealtWith = weightedRandomIndexes.choicesDealtWith; + + let voteSums = weightedRandomIndexes.weightSums; + //convert these into the votes + for (let i = 0; i < finalSponsorTimeIndexes.length; i++) { + //it should use the sum of votes, since anyone upvoting a similar sponsor is upvoting the existence of that sponsor. + votes[finalSponsorTimeIndexes[i]] = voteSums[i]; + } + + //find the indexes never dealt with and add them + for (let i = 0; i < sponsorTimes.length; i++) { + if (!finalSponsorTimeIndexesDealtWith.includes(i)) { + finalSponsorTimeIndexes.push(i) + } + } + + //if there are too many indexes, find the best 4 + if (finalSponsorTimeIndexes.length > 8) { + finalSponsorTimeIndexes = getWeightedRandomChoice(finalSponsorTimeIndexes, votes, 8).finalChoices; + } + + //convert this to a final array to return + let finalSponsorTimes = []; + for (let i = 0; i < finalSponsorTimeIndexes.length; i++) { + finalSponsorTimes.push(sponsorTimes[finalSponsorTimeIndexes[i]]); + } + + //convert this to a final array of UUIDs as well + let finalUUIDs = []; + for (let i = 0; i < finalSponsorTimeIndexes.length; i++) { + finalUUIDs.push(UUIDs[finalSponsorTimeIndexes[i]]); + } + + return { + sponsorTimes: finalSponsorTimes, + UUIDs: finalUUIDs + }; +} + + + +module.exports = function (req, res) { + const videoID = req.body.videoID || req.query.videoID; + // Default to sponsor + // If using params instead of JSON, only one category can be pulled + const categories = req.body.categories || [req.query.category] ["sponsor"]; + + let sponsorTimes = []; + let votes = [] + let UUIDs = []; + + let hashedIP = getHash(getIP(req) + config.globalSalt); + + try { + let rows = db.prepare("SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? ORDER BY startTime").all(videoID); + + for (let i = 0; i < rows.length; i++) { + //check if votes are above -1 + if (rows[i].votes < -1) { + //too untrustworthy, just ignore it + continue; + } + + //check if shadowHidden + //this means it is hidden to everyone but the original ip that submitted it + if (rows[i].shadowHidden == 1) { + //get the ip + //await the callback + let hashedIPRow = privateDB.prepare("SELECT hashedIP FROM sponsorTimes WHERE videoID = ?").all(videoID); + + if (!hashedIPRow.some((e) => e.hashedIP === hashedIP)) { + //this isn't their ip, don't send it to them + continue; + } + } + + sponsorTimes.push([]); + + let index = sponsorTimes.length - 1; + + sponsorTimes[index][0] = rows[i].startTime; + sponsorTimes[index][1] = rows[i].endTime; + + votes[index] = rows[i].votes; + UUIDs[index] = rows[i].UUID; + } + + if (sponsorTimes.length == 0) { + res.sendStatus(404); + return; + } + + organisedData = getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs); + sponsorTimes = organisedData.sponsorTimes; + UUIDs = organisedData.UUIDs; + + if (sponsorTimes.length == 0) { + res.sendStatus(404); + } else { + //send result + res.send({ + sponsorTimes: sponsorTimes, + UUIDs: UUIDs + }) + } + } catch(error) { + console.error(error); + res.send(500); + } +} \ No newline at end of file diff --git a/src/routes/postSkipSegments.js b/src/routes/postSkipSegments.js index 6562d94..08a5c7f 100644 --- a/src/routes/postSkipSegments.js +++ b/src/routes/postSkipSegments.js @@ -4,6 +4,8 @@ var databases = require('../databases/databases.js'); var db = databases.db; var privateDB = databases.privateDB; var YouTubeAPI = require('../utils/youtubeAPI.js'); +var request = require('request'); +var isoDurations = require('iso8601-duration'); var getHash = require('../utils/getHash.js'); var getIP = require('../utils/getIP.js'); @@ -34,7 +36,7 @@ function sendDiscordNotification(userID, videoID, UUID, segmentInfo) { let userSubmissionCountRow = db.prepare("SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?").get(userID); // If it is a first time submission - if (userSubmissionCountRow.submissionCount === 0) { + if (userSubmissionCountRow.submissionCount <= 1) { YouTubeAPI.videos.list({ part: "snippet", id: videoID @@ -78,6 +80,47 @@ function sendDiscordNotification(userID, videoID, UUID, segmentInfo) { } } +// submission: {videoID, startTime, endTime} +// callback: function(reject: "String containing reason the submission was rejected") +// returns: string when an error, false otherwise +async function autoModerateSubmission(submission, callback) { + // Get the video information from the youtube API + if (config.youtubeAPI !== null) { + let {err, data} = await new Promise((resolve, reject) => { + YouTubeAPI.videos.list({ + part: "contentDetails", + id: submission.videoID + }, (err, data) => resolve({err, data})); + }); + + if (err) { + return "Couldn't get video information."; + } else { + // Check to see if video exists + if (data.pageInfo.totalResults === 0) { + callback("No video exists with id " + submission.videoID); + } else { + let duration = data.items[0].contentDetails.duration; + duration = isoDurations.toSeconds(isoDurations.parse(duration)); + + // Reject submission if over 80% of the video + if ((submission.endTime - submission.startTime) > (duration/100)*80) { + return "Sponsor segment is over 80% of the video."; + } else { + return false; + } + } + } + + } else { + console.log("Skipped YouTube API"); + + // Can't moderate the submission without calling the youtube API + // so allow by default. + return; + } +} + module.exports = async function postSkipSegments(req, res) { let videoID = req.query.videoID || req.body.videoID; let userID = req.query.userID || req.body.userID; @@ -124,10 +167,17 @@ module.exports = async function postSkipSegments(req, res) { } //check if this info has already been submitted before - let duplicateCheck2Row = - db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").get([startTime, endTime, videoID]); - if (duplicateCheck2Row == null) { - res.sendStatus(409); + let duplicateCheck2Row = db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").get(startTime, endTime, videoID); + if (duplicateCheck2Row !== null) { + // console.log(duplicateCheck2Row) + // console.log(db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").all(1,10,"dQw4w9WgXcQ")) + // res.sendStatus(409); + // return; + } + + let autoModerateResult = await autoModerateSubmission({videoID, startTime, endTime}); + if (autoModerateResult) { + res.status(403).send("Request rejected by auto moderator: " + autoModerateResult); return; } } @@ -184,8 +234,10 @@ module.exports = async function postSkipSegments(req, res) { let UUID = getHash("v2-categories" + videoID + segmentInfo.segment[0] + segmentInfo.segment[1] + segmentInfo.category + userID, 1); + console.log(UUID) + try { - db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, segmentInfo.segment[0], + db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned); //add to private db as well @@ -196,7 +248,7 @@ module.exports = async function postSkipSegments(req, res) { //a DB change probably occurred res.sendStatus(502); console.log("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " + - segmentInfo.segment[1] + ", " + userID + ", " + segmentInfo.category); + segmentInfo.segment[1] + ", " + userID + ", " + segmentInfo.category + ". " + err); return; } @@ -207,6 +259,6 @@ module.exports = async function postSkipSegments(req, res) { } catch (err) { console.error(err); - res.send(500); + res.sendStatus(500); } } diff --git a/src/utils/youtubeAPI.js b/src/utils/youtubeAPI.js index 7449815..c28f882 100644 --- a/src/utils/youtubeAPI.js +++ b/src/utils/youtubeAPI.js @@ -2,8 +2,18 @@ var config = require('../config.js'); // YouTube API const YouTubeAPI = require("youtube-api"); -YouTubeAPI.authenticate({ - type: "key", - key: config.youtubeAPIKey -}); -module.exports = YouTubeAPI; \ No newline at end of file + +var exportObject; +// If in test mode, return a mocked youtube object +// otherwise return an authenticated youtube api +if (config.mode === "test") { + exportObject = require("../../test/youtubeMock.js"); +} else { + YouTubeAPI.authenticate({ + type: "key", + key: config.youtubeAPIKey + }); + exportObject = YouTubeAPI; +} + +module.exports = exportObject; \ No newline at end of file diff --git a/test/cases/getSavedTimeForUser.js b/test/cases/getSavedTimeForUser.js index 5488c67..e3976c1 100644 --- a/test/cases/getSavedTimeForUser.js +++ b/test/cases/getSavedTimeForUser.js @@ -5,16 +5,16 @@ var getHash = require('../../src/utils/getHash.js'); describe('getSavedTimeForUser', () => { before(() => { - db.exec("INSERT INTO sponsorTimes VALUES ('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '"+getHash("testman")+"', 0, 50, 0)"); + db.exec("INSERT INTO sponsorTimes VALUES ('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '" + getHash("testman") + "', 0, 50, 'sponsor', 0)"); }); it('Should be able to get a 200', (done) => { request.get(utils.getbaseURL() + "/api/getSavedTimeForUser?userID=testman", null, (err, res, body) => { - if (err) done(false); + if (err) done("couldn't call endpoint"); else if (res.statusCode !== 200) done("non 200"); - else done(); + else done(); // pass }); }); }); \ No newline at end of file diff --git a/test/cases/getSponsorTime.js b/test/cases/getSponsorTime.js index 0565d28..306f563 100644 --- a/test/cases/getSponsorTime.js +++ b/test/cases/getSponsorTime.js @@ -19,17 +19,17 @@ var utils = require('../utils.js'); describe('getVideoSponsorTime', () => { before(() => { - db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 0)"); - db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 0)"); + db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 'sponsor', 0)"); + db.exec("INSERT INTO sponsorTimes VALUES ('testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 'sponsor', 0)"); }); it('Should be able to get a time', (done) => { request.get(utils.getbaseURL() + "/api/getVideoSponsorTimes?videoID=testtesttest", null, (err, res, body) => { - if (err) done(false); + if (err) done("Couldn't call endpoint"); else if (res.statusCode !== 200) done("non 200"); - else done(); + else done(); // pass }); }); @@ -37,9 +37,9 @@ describe('getVideoSponsorTime', () => { request.get(utils.getbaseURL() + "/api/getVideoSponsorTimes?videoID=notarealvideo", null, (err, res, body) => { - if (err) done(false); + if (err) done("couldn't call endpoint"); else if (res.statusCode !== 404) done("non 404 respone code: " + res.statusCode); - else done(); + else done(); // pass }); }); @@ -48,9 +48,9 @@ describe('getVideoSponsorTime', () => { request.get(utils.getbaseURL() + "/api/getVideoSponsorTimes?videoID=testtesttest&fakeparam=hello", null, (err, res, body) => { - if (err) done(false); + if (err) done("couldn't callendpoint"); else if (res.statusCode !== 200) done("non 200"); - else done(); + else done(); // pass }); }); @@ -58,9 +58,10 @@ describe('getVideoSponsorTime', () => { request.get(utils.getbaseURL() + "/api/getVideoSponsorTimes?videoID=testtesttest,test", null, (err, res, body) => { - if (err) done(false); + if (err) done("couln't call endpoint"); else if (res.statusCode !== 200) done("non 200 response: " + res.statusCode); - else (JSON.parse(body).UUIDs[0] === 'uuid-1') && done(); + else if (JSON.parse(body).UUIDs[0] === 'uuid-1') done(); // pass + else done("couldn't parse response"); }); }); @@ -68,14 +69,14 @@ describe('getVideoSponsorTime', () => { request.get(utils.getbaseURL() + "/api/getVideoSponsorTimes?videoID=testtesttest", null, (err, res, body) => { - if (err) done(false); + if (err) done("couldn't call endpoint"); else if (res.statusCode !== 200) done("non 200"); else { let parsedBody = JSON.parse(body); if (parsedBody.sponsorTimes[0][0] === 1 && parsedBody.sponsorTimes[0][1] === 11 && parsedBody.UUIDs[0] === 'uuid-0') { - done(); + done(); // pass } else { done("Wrong data was returned + " + parsedBody); } diff --git a/test/cases/oldSubmitSponsorTimes.js b/test/cases/oldSubmitSponsorTimes.js index d205df5..bf3ac7a 100644 --- a/test/cases/oldSubmitSponsorTimes.js +++ b/test/cases/oldSubmitSponsorTimes.js @@ -9,35 +9,37 @@ var db = databases.db; describe('postVideoSponsorTime (Old submission method)', () => { it('Should be able to submit a time (GET)', (done) => { request.get(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=djgofQKWmXc&startTime=1&endTime=10&userID=test", null, + + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcQ&startTime=1&endTime=10&userID=test", null, (err, res, body) => { - if (err) done(false); + if (err) done(err); else if (res.statusCode === 200) { - let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get(videoID); + let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get("dQw4w9WgXcQ"); if (row.startTime === 1 && row.endTime === 10 && row.category === "sponsor") { done() - return; + } else { + done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); } + } else { + done("Status code was " + res.statusCode); } - - done(false); }); }); it('Should be able to submit a time (POST)', (done) => { request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=djgofQKWmXc&startTime=1&endTime=10&userID=test", null, + + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcE&startTime=1&endTime=11&userID=test", null, (err, res, body) => { - if (err) done(false); + if (err) done(err); else if (res.statusCode === 200) { - let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get(videoID); - if (row.startTime === 1 && row.endTime === 10 && row.category === "sponsor") { + let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get("dQw4w9WgXcE"); + if (row.startTime === 1 && row.endTime === 11 && row.category === "sponsor") { done() - return; + } else { + done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); } + } else { + done("Status code was " + res.statusCode); } - - done(false); }); }); @@ -45,9 +47,9 @@ describe('postVideoSponsorTime (Old submission method)', () => { request.get(utils.getbaseURL() + "/api/postVideoSponsorTimes?startTime=1&endTime=10&userID=test", null, (err, res, body) => { - if (err) done(false); + if (err) done(err); if (res.statusCode === 400) done(); - else done(false); + else done("Status code was: " + res.statusCode); }); }); }); \ No newline at end of file diff --git a/test/cases/postSkipSegments.js b/test/cases/postSkipSegments.js index 446d802..05c7374 100644 --- a/test/cases/postSkipSegments.js +++ b/test/cases/postSkipSegments.js @@ -9,172 +9,204 @@ var db = databases.db; describe('postSkipSegments', () => { it('Should be able to submit a single time (Params method)', (done) => { request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=djgofQKWmXc&startTime=1&endTime=10&userID=test&category=sponsor", null, + + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcR&startTime=2&endTime=10&userID=test&category=sponsor", null, (err, res, body) => { - if (err) done(false); + if (err) done(err); else if (res.statusCode === 200) { - let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get(videoID); - if (row.startTime === 1 && row.endTime === 10 && row.category === "sponsor") { + let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get("dQw4w9WgXcR"); + if (row.startTime === 2 && row.endTime === 10 && row.category === "sponsor") { done() - return; + } else { + done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); } + } else { + done("Status code was " + res.statusCode); } - - done(false); }); }); it('Should be able to submit a single time (JSON method)', (done) => { request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", JSON.stringify({ - body: { - videoID: "djgofQKWmXc", + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcF", segments: [{ segment: [0, 10], category: "sponsor" }] } - }), + }, (err, res, body) => { - if (err) done(false); + if (err) done(err); else if (res.statusCode === 200) { - let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get(videoID); + let row = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").get("dQw4w9WgXcF"); if (row.startTime === 0 && row.endTime === 10 && row.category === "sponsor") { done() - return; + } else { + done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); } + } else { + done("Status code was " + res.statusCode); } - - done(false); }); }); it('Should be able to submit multiple times (JSON method)', (done) => { request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", JSON.stringify({ - body: { - videoID: "djgofQKWmXc", + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcQ", segments: [{ - segment: [0, 10], + segment: [3, 10], category: "sponsor" }, { segment: [30, 60], category: "intro" }] } - }), + }, (err, res, body) => { - if (err) done(false); + if (err) done(err); else if (res.statusCode === 200) { - let rows = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").all(videoID); - - if (rows.length !== 2) done(false); - for (const row of rows) { - if (row.startTime !== 1 || row.endTime !== 10 || row.category !== "sponsor") { - done(false) - return; + let rows = db.prepare("SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?").all("dQw4w9WgXcR"); + let success = true; + if (rows.length === 2) { + for (const row of rows) { + if ((row.startTime !== 3 || row.endTime !== 10 || row.category !== "sponsor") && + (row.startTime !== 30 || row.endTime !== 60 || row.category !== "intro")) { + success = false; + break; + } } } - done() + if (success) done(); + else done("Submitted times were not saved. Actual submissions: " + JSON.stringify(row)); + } else { + done("Status code was " + res.statusCode); } - - done(false); + }); + }); + + it('Should be rejected if over 80% of the video', (done) => { + request.get(utils.getbaseURL() + + "/api/postVideoSponsorTimes?videoID=qqwerty&startTime=30&endTime=1000000&userID=testing", null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 403) done(); // pass + else done("non 403 status code: " + res.statusCode + " ("+body+")"); + }); + }); + + it('Should be rejected if not a valid videoID', (done) => { + request.get(utils.getbaseURL() + + "/api/postVideoSponsorTimes?videoID=knownWrongID&startTime=30&endTime=1000000&userID=testing", null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 403) done(); // pass + else done("non 403 status code: " + res.statusCode + " ("+body+")"); }); }); it('Should return 400 for missing params (Params method)', (done) => { request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes?startTime=1&endTime=10&userID=test", null, + + "/api/postVideoSponsorTimes?startTime=9&endTime=10&userID=test", null, (err, res, body) => { - if (err) done(false); + if (err) done(true); if (res.statusCode === 400) done(); - else done(false); + else done(true); }); }); it('Should return 400 for missing params (JSON method) 1', (done) => { request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", JSON.stringify({ - body: { + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", segments: [{ - segment: [0, 10], + segment: [9, 10], category: "sponsor" }, { - segment: [30, 60], + segment: [31, 60], category: "intro" }] } - }), + }, (err, res, body) => { - if (err) done(false); - else if (res.statusCode === 200) done(); - else done(false); + if (err) done(true); + else if (res.statusCode === 400) done(); + else done(true); }); }); it('Should return 400 for missing params (JSON method) 2', (done) => { request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", JSON.stringify({ - body: { - videoID: "djgofQKWmXc" + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcQ" } - }), + }, (err, res, body) => { - if (err) done(false); - else if (res.statusCode === 200) done(); - else done(false); + if (err) done(true); + else if (res.statusCode === 400) done(); + else done(true); }); }); it('Should return 400 for missing params (JSON method) 3', (done) => { request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", JSON.stringify({ - body: { - videoID: "djgofQKWmXc", + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcQ", segments: [{ segment: [0], category: "sponsor" }, { - segment: [30, 60], + segment: [31, 60], category: "intro" }] } - }), + }, (err, res, body) => { - if (err) done(false); - else if (res.statusCode === 200) done(); - else done(false); + if (err) done(true); + else if (res.statusCode === 400) done(); + else done(true); }); }); it('Should return 400 for missing params (JSON method) 4', (done) => { request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", JSON.stringify({ - body: { - videoID: "djgofQKWmXc", + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcQ", segments: [{ - segment: [0, 10] + segment: [9, 10] }, { - segment: [30, 60], + segment: [31, 60], category: "intro" }] } - }), + }, (err, res, body) => { - if (err) done(false); - else if (res.statusCode === 200) done(); - else done(false); + if (err) done(true); + else if (res.statusCode === 400) done(); + else done(true); }); }); it('Should return 400 for missing params (JSON method) 5', (done) => { request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", JSON.stringify({ - body: { - videoID: "djgofQKWmXc" + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcQ" } - }), + }, (err, res, body) => { - if (err) done(false); - else if (res.statusCode === 200) done(); - else done(false); + if (err) done(true); + else if (res.statusCode === 400) done(); + else done(true); }); }); }); \ No newline at end of file diff --git a/test/databases/_private.db.sql b/test/databases/_private.db.sql index 317603a..b93aaee 100644 --- a/test/databases/_private.db.sql +++ b/test/databases/_private.db.sql @@ -1,8 +1,4 @@ BEGIN TRANSACTION; -DROP TABLE IF EXISTS "shadowBannedUsers"; -DROP TABLE IF EXISTS "votes"; -DROP TABLE IF EXISTS "sponsorTimes"; - CREATE TABLE IF NOT EXISTS "shadowBannedUsers" ( "userID" TEXT NOT NULL ); diff --git a/test/databases/_sponsorTimes.db.sql b/test/databases/_sponsorTimes.db.sql index bbf501e..dfca8bf 100644 --- a/test/databases/_sponsorTimes.db.sql +++ b/test/databases/_sponsorTimes.db.sql @@ -1,8 +1,4 @@ BEGIN TRANSACTION; -DROP TABLE IF EXISTS "vipUsers"; -DROP TABLE IF EXISTS "sponsorTimes"; -DROP TABLE IF EXISTS "userNames"; - CREATE TABLE IF NOT EXISTS "vipUsers" ( "userID" TEXT NOT NULL ); @@ -15,6 +11,7 @@ CREATE TABLE IF NOT EXISTS "sponsorTimes" ( "userID" TEXT NOT NULL, "timeSubmitted" INTEGER NOT NULL, "views" INTEGER NOT NULL, + "category" TEXT NOT NULL, "shadowHidden" INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS "userNames" ( diff --git a/test/youtubeMock.js b/test/youtubeMock.js new file mode 100644 index 0000000..56300fd --- /dev/null +++ b/test/youtubeMock.js @@ -0,0 +1,38 @@ +/* +YouTubeAPI.videos.list({ + part: "snippet", + id: videoID +}, function (err, data) {}); + */ + + // https://developers.google.com/youtube/v3/docs/videos + +const YouTubeAPI = { + videos: { + list: (obj, callback) => { + if (obj.videoID === "knownWrongID") { + callback(undefined, { + pageInfo: { + totalResults: 0 + }, + items: [] + }); + } else { + callback(undefined, { + pageInfo: { + totalResults: 1 + }, + items: [ + { + contentDetails: { + duration: "PT1H23M30S" + } + } + ] + }); + } + } + } +}; + +module.exports = YouTubeAPI; \ No newline at end of file