diff --git a/src/app.ts b/src/app.ts index ef89089..49d1224 100644 --- a/src/app.ts +++ b/src/app.ts @@ -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,10 @@ 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)); @@ -212,4 +218,4 @@ function setupRoutes(router: Router) { }); } } -/* eslint-enable @typescript-eslint/no-misused-promises */ \ No newline at end of file +/* eslint-enable @typescript-eslint/no-misused-promises */ diff --git a/src/routes/getVideoLabel.ts b/src/routes/getVideoLabel.ts new file mode 100644 index 0000000..02ba7c5 --- /dev/null +++ b/src/routes/getVideoLabel.ts @@ -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 { + 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> { + const segments: SBRecord = {}; + + try { + type SegmentWithHashPerVideoID = SBRecord; + + 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 { + 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; + + if (hashedVideoIDPrefix.length === 4) { + return await QueryCacher.get(fetchFromDB, videoLabelsHashKey(hashedVideoIDPrefix, service)); + } + + return await fetchFromDB(); +} + +async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): Promise { + 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; + + return await QueryCacher.get(fetchFromDB, videoLabelsKey(videoID, service)); +} + +function chooseSegment(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 { + 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 { + 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 +}; diff --git a/src/routes/getVideoLabelByHash.ts b/src/routes/getVideoLabelByHash.ts new file mode 100644 index 0000000..e3a3615 --- /dev/null +++ b/src/routes/getVideoLabelByHash.ts @@ -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 { + 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); +} diff --git a/src/types/segments.model.ts b/src/types/segments.model.ts index 841bb99..47be636 100644 --- a/src/types/segments.model.ts +++ b/src/types/segments.model.ts @@ -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; diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index cb1faeb..1b3f6a1 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -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)); } } diff --git a/src/utils/redisKeys.ts b/src/utils/redisKeys.ts index 86cfcef..e482e10 100644 --- a/src/utils/redisKeys.ts +++ b/src/utils/redisKeys.ts @@ -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}`; } \ No newline at end of file diff --git a/test/cases/getVideoLabelByHash.ts b/test/cases/getVideoLabelByHash.ts new file mode 100644 index 0000000..650867e --- /dev/null +++ b/test/cases/getVideoLabelByHash.ts @@ -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)); + }); +}); diff --git a/test/cases/getVideoLabels.ts b/test/cases/getVideoLabels.ts new file mode 100644 index 0000000..7202d2c --- /dev/null +++ b/test/cases/getVideoLabels.ts @@ -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)); + }); +});