Merge pull request #463 from mchangrh/fullVideoLabels

Full Video Labels endpoint
This commit is contained in:
Ajay Ramachandran
2022-10-28 17:08:05 -04:00
committed by GitHub
8 changed files with 569 additions and 3 deletions

View File

@@ -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 */
/* eslint-enable @typescript-eslint/no-misused-promises */

167
src/routes/getVideoLabel.ts Normal file
View 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
};

View 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);
}

View File

@@ -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;

View File

@@ -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));
}
}

View File

@@ -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}`;
}

View 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));
});
});

View 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));
});
});