From 840ccb517e31f7fc82f8ad0da1a28c97d36f9acd Mon Sep 17 00:00:00 2001 From: Michael C Date: Fri, 27 Aug 2021 21:46:39 -0400 Subject: [PATCH 1/3] add getSearchSegments endpoint --- api-prop.md | 48 +++++++++++ src/app.ts | 4 + src/routes/getSearchSegments.ts | 143 ++++++++++++++++++++++++++++++++ src/types/segments.model.ts | 2 + 4 files changed, 197 insertions(+) create mode 100644 api-prop.md create mode 100644 src/routes/getSearchSegments.ts diff --git a/api-prop.md b/api-prop.md new file mode 100644 index 0000000..e8cd3d6 --- /dev/null +++ b/api-prop.md @@ -0,0 +1,48 @@ +GET /api/searchSegments +Input: (URL Params, translated 1-1 to JSON) +```ts +{ + // same as skipSegments + videoID: string, + + category: string + categories: string[] + + actionType: string + actionTypes: string[] + + service: string + // end skipSegments + + page: int // page to start from (default 0) + + minVotes: int // default -2, vote threshold, inclusive + maxVotes: int // default infinite, vote threshold, inclusive + + minViews: int // default 0 - view threshold, inclusive + maxViews: int // default infinite - view threshold, inclusive + + locked: boolean // default true - if false, dont't show segments that are locked + hidden: boolean // default true - if false, don't show segment that are hidden/ shadowhidden + ignored: boolean // default true - if false, don't show segments that are hidden or below vote threshold +} +``` + +Response: (JSON) +```ts + segmentCount: int, // total number of segements matching query + page: int, // page number + segments: [{ // array of this object, max 10 + UUID: string, + timeSubmitted: int, // time submitted + startTime: int, // start time in seconds + endTime: int, // end time in seconds + category: string, // category of segment + actionType: string, // action type of segment + votes: int, // number of votes + views: int // number of views + locked: int, // locked + hidden: int, // hidden + shadowHidden: int, // shadowHidden + }] +``` \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 5e97f05..706d53b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -35,6 +35,7 @@ import {postPurgeAllSegments} from "./routes/postPurgeAllSegments"; import {getUserID} from "./routes/getUserID"; import {getLockCategories} from "./routes/getLockCategories"; import {getLockCategoriesByHash} from "./routes/getLockCategoriesByHash"; +import {endpoint as getSearchSegments } from "./routes/getSearchSegments"; import ExpressPromiseRouter from "express-promise-router"; import { Server } from "http"; @@ -164,6 +165,9 @@ function setupRoutes(router: Router) { // get privacy protecting lock categories functions router.get("/api/lockCategories/:prefix", getLockCategoriesByHash); + // get all segments that match a search + router.get("/api/searchSegments", getSearchSegments); + if (config.postgres) { router.get("/database", (req, res) => dumpDatabase(req, res, true)); router.get("/database.json", (req, res) => dumpDatabase(req, res, false)); diff --git a/src/routes/getSearchSegments.ts b/src/routes/getSearchSegments.ts new file mode 100644 index 0000000..a73f9ca --- /dev/null +++ b/src/routes/getSearchSegments.ts @@ -0,0 +1,143 @@ +import { Request, Response } from "express"; +import { db } from "../databases/databases"; +import { ActionType, Category, DBSegment, Service, VideoID } from "../types/segments.model"; +const segmentsPerPage = 10; + +type searchSegmentResponse = { + segmentCount: number, + page: number, + segments: DBSegment[] +}; + +async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): Promise { + return db.prepare( + "all", + `SELECT "UUID", "timeSubmitted", "startTime", "endTime", "category", "actionType", "votes", "views", "locked", "hidden", "shadowHidden", FROM "sponsorTimes" + WHERE "videoID" = ? AND "service" = ? ORDER BY "UUID"`, + [videoID, service] + ) as Promise; +} + +/** + * + * Returns what would be sent to the client. + * Will respond with errors if required. Returns false if it errors. + * + * @param req + * @param res + * + * @returns + */ +async function handleGetSegments(req: Request, res: Response): Promise { + const videoID = req.query.videoID as VideoID; + if (!videoID) { + res.status(400).send("videoID not specified"); + return false; + } + // Default to sponsor + // If using params instead of JSON, only one category can be pulled + const categories: Category[] = req.query.categories + ? JSON.parse(req.query.categories as string) + : req.query.category + ? Array.isArray(req.query.category) + ? req.query.category + : [req.query.category] + : ["sponsor"]; + if (!Array.isArray(categories)) { + res.status(400).send("Categories parameter does not match format requirements."); + return false; + } + + 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]; + if (!Array.isArray(actionTypes)) { + res.status(400).send("actionTypes parameter does not match format requirements."); + return false; + } + + let service: Service = req.query.service ?? req.body.service ?? Service.YouTube; + if (!Object.values(Service).some((val) => val == service)) { + service = Service.YouTube; + } + + const page: number = req.query.page ?? req.body.page ?? 0; + + const minVotes: number = req.query.minVotes ?? req.body.minVotes ?? -2; + const maxVotes: number = req.query.maxVotes ?? req.body.maxVotes ?? Infinity; + + const minViews: number = req.query.minViews ?? req.body.minViews ?? 0; + const maxViews: number = req.query.maxViews ?? req.body.maxViews ?? Infinity; + + const locked: boolean = req.query.locked ?? req.body.locked ?? true; + const hidden: boolean = req.query.hidden ?? req.body.hidden ?? true; + const ignored: boolean = req.query.ignored ?? req.body.ignored ?? true; + + const filters = { + minVotes, + maxVotes, + minViews, + maxViews, + locked, + hidden, + ignored + }; + + const segments = await getSegmentsFromDBByVideoID(videoID, service); + + if (segments === null || segments === undefined) { + res.sendStatus(500); + return false; + } + + if (segments.length === 0) { + res.sendStatus(404); + return false; + } + + return filterSegments(segments, page, filters); +} + +function filterSegments(segments: DBSegment[], page: number, filters: Record) { + const startIndex = 0+(page*segmentsPerPage); + const endIndex = segmentsPerPage+(page*segmentsPerPage); + const filteredSegments = segments.filter((segment) => + (!(segment.votes <= filters.minVotes || segment.votes >= filters.maxVotes) + || (segment.views <= filters.minViews || segment.views >= filters.maxViews) + || (filters.locked && segment.locked) + || (filters.hidden && segment.hidden) + || (filters.ignored && (segment.hidden || segment.shadowHidden)) + ) + // return false if any of the conditions are met + // return true if none of the conditions are met + ); + return { + segmentCount: filteredSegments.length, + page, + segments: filteredSegments.slice(startIndex, endIndex) + }; +} + + +async function endpoint(req: Request, res: Response): Promise { + try { + const segmentResponse = await handleGetSegments(req, res); + // If false, res.send has already been called + if (segmentResponse) { + //send result + return res.send(segmentResponse); + } + } catch (err) { + if (err instanceof SyntaxError) { + return res.status(400).send("Invalid array in parameters"); + } else return res.sendStatus(500); + } +} + +export { + endpoint +}; diff --git a/src/types/segments.model.ts b/src/types/segments.model.ts index be4745f..b863a15 100644 --- a/src/types/segments.model.ts +++ b/src/types/segments.model.ts @@ -53,7 +53,9 @@ export interface DBSegment { UUID: SegmentUUID; userID: UserID; votes: number; + views: number; locked: boolean; + hidden: boolean; required: boolean; // Requested specifically from the client shadowHidden: Visibility; videoID: VideoID; From 0d6731fcc6f05a034a7dd02e28915d4b01d26e2a Mon Sep 17 00:00:00 2001 From: Michael C Date: Fri, 27 Aug 2021 21:47:30 -0400 Subject: [PATCH 2/3] remove accidental inclusion of api-prop --- api-prop.md | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) delete mode 100644 api-prop.md diff --git a/api-prop.md b/api-prop.md deleted file mode 100644 index e8cd3d6..0000000 --- a/api-prop.md +++ /dev/null @@ -1,48 +0,0 @@ -GET /api/searchSegments -Input: (URL Params, translated 1-1 to JSON) -```ts -{ - // same as skipSegments - videoID: string, - - category: string - categories: string[] - - actionType: string - actionTypes: string[] - - service: string - // end skipSegments - - page: int // page to start from (default 0) - - minVotes: int // default -2, vote threshold, inclusive - maxVotes: int // default infinite, vote threshold, inclusive - - minViews: int // default 0 - view threshold, inclusive - maxViews: int // default infinite - view threshold, inclusive - - locked: boolean // default true - if false, dont't show segments that are locked - hidden: boolean // default true - if false, don't show segment that are hidden/ shadowhidden - ignored: boolean // default true - if false, don't show segments that are hidden or below vote threshold -} -``` - -Response: (JSON) -```ts - segmentCount: int, // total number of segements matching query - page: int, // page number - segments: [{ // array of this object, max 10 - UUID: string, - timeSubmitted: int, // time submitted - startTime: int, // start time in seconds - endTime: int, // end time in seconds - category: string, // category of segment - actionType: string, // action type of segment - votes: int, // number of votes - views: int // number of views - locked: int, // locked - hidden: int, // hidden - shadowHidden: int, // shadowHidden - }] -``` \ No newline at end of file From e9bffd0cf27af9def360e422e111a6af703e72e5 Mon Sep 17 00:00:00 2001 From: Michael C Date: Wed, 1 Sep 2021 13:53:06 -0400 Subject: [PATCH 3/3] add test cases and fixed bugs --- src/routes/getSearchSegments.ts | 39 ++--- test/cases/getSearchSegments.ts | 254 ++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+), 19 deletions(-) create mode 100644 test/cases/getSearchSegments.ts diff --git a/src/routes/getSearchSegments.ts b/src/routes/getSearchSegments.ts index a73f9ca..5a86b55 100644 --- a/src/routes/getSearchSegments.ts +++ b/src/routes/getSearchSegments.ts @@ -12,8 +12,8 @@ type searchSegmentResponse = { async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): Promise { return db.prepare( "all", - `SELECT "UUID", "timeSubmitted", "startTime", "endTime", "category", "actionType", "votes", "views", "locked", "hidden", "shadowHidden", FROM "sponsorTimes" - WHERE "videoID" = ? AND "service" = ? ORDER BY "UUID"`, + `SELECT "UUID", "timeSubmitted", "startTime", "endTime", "category", "actionType", "votes", "views", "locked", "hidden", "shadowHidden" FROM "sponsorTimes" + WHERE "videoID" = ? AND "service" = ? ORDER BY "timeSubmitted"`, [videoID, service] ) as Promise; } @@ -35,14 +35,13 @@ async function handleGetSegments(req: Request, res: Response): Promise) { +function filterSegments(segments: DBSegment[], page: number, filters: Record) { const startIndex = 0+(page*segmentsPerPage); const endIndex = segmentsPerPage+(page*segmentsPerPage); const filteredSegments = segments.filter((segment) => - (!(segment.votes <= filters.minVotes || segment.votes >= filters.maxVotes) - || (segment.views <= filters.minViews || segment.views >= filters.maxViews) - || (filters.locked && segment.locked) - || (filters.hidden && segment.hidden) - || (filters.ignored && (segment.hidden || segment.shadowHidden)) - ) + !((segment.votes < filters.minVotes || segment.votes > filters.maxVotes) + || (segment.views < filters.minViews || segment.views > filters.maxViews) + || (!filters.locked && segment.locked) + || (!filters.hidden && segment.hidden) + || (!filters.ignored && (segment.hidden || segment.shadowHidden)) + || (filters.categories.length > 0 && !filters.categories.includes(segment.category))) // return false if any of the conditions are met // return true if none of the conditions are met ); diff --git a/test/cases/getSearchSegments.ts b/test/cases/getSearchSegments.ts new file mode 100644 index 0000000..8f3aa61 --- /dev/null +++ b/test/cases/getSearchSegments.ts @@ -0,0 +1,254 @@ +import fetch from "node-fetch"; +import {db} from "../../src/databases/databases"; +import {Done, getbaseURL} from "../utils"; +import assert from "assert"; + +describe("getSearchSegments", () => { + before(async () => { + const query = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "views", "locked", "hidden", "shadowHidden", "timeSubmitted", "UUID", "userID", "category", "actionType") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + await db.prepare("run", query, ["searchTest0", 0, 1, 2, 0, 0, 0, 0, 1, "search-normal", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest0", 0, 2, -2, 0, 0, 0, 0, 2, "search-downvote", "testman", "selfpromo", "skip",]); + await db.prepare("run", query, ["searchTest0", 0, 3, 1, 0, 1, 0, 0, 3, "search-locked", "testman", "interaction", "skip"]); + await db.prepare("run", query, ["searchTest0", 0, 4, 1, 0, 0, 1, 0, 4, "search-hidden", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest0", 0, 5, 1, 0, 0, 0, 1, 5, "search-shadowhidden", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest1", 1, 2, 1, 5, 0, 0, 0, 6, "search-lowview", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest1", 1, 3, 1, 50, 0, 0, 0, 7, "search-highview", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest2", 1, 4, -1, 0, 0, 0, 0, 8, "search-lowvote", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest2", 2, 3, 0, 0, 0, 0, 0, 9, "search-zerovote", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest2", 2, 4, 50, 0, 0, 0, 0, 10, "search-highvote", "testman", "sponsor", "skip"]); + // page + await db.prepare("run", query, ["searchTest4", 3, 4, 1, 0, 0, 0, 0, 10, "search-page1-1", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest4", 3, 5, 1, 0, 0, 0, 0, 11, "search-page1-2", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest4", 3, 6, 1, 0, 0, 0, 0, 12, "search-page1-3", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest4", 3, 7, 1, 0, 0, 0, 0, 13, "search-page1-4", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest4", 3, 8, 1, 0, 0, 0, 0, 14, "search-page1-5", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest4", 3, 9, 1, 0, 0, 0, 0, 15, "search-page1-6", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest4", 3, 10, 1, 0, 0, 0, 0, 16, "search-page1-7", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest4", 3, 11, 1, 0, 0, 0, 0, 17, "search-page1-8", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest4", 3, 12, 1, 0, 0, 0, 0, 18, "search-page1-9", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest4", 3, 13, 1, 0, 0, 0, 0, 19, "search-page1-10", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest4", 3, 14, 1, 0, 0, 0, 0, 20, "search-page2-1", "testman", "sponsor", "skip"]); + await db.prepare("run", query, ["searchTest4", 3, 15, 1, 0, 0, 0, 0, 21, "search-page2-2", "testman", "sponsor", "skip"]); + return; + }); + + it("Should be able to show all segments under searchTest0", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest0`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 5); + assert.strictEqual(data.page, 0); + assert.strictEqual(segments[0].UUID, "search-normal"); + assert.strictEqual(segments[1].UUID, "search-downvote"); + assert.strictEqual(segments[2].UUID, "search-locked"); + assert.strictEqual(segments[3].UUID, "search-hidden"); + assert.strictEqual(segments[4].UUID, "search-shadowhidden"); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to filter by category", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest0&category=selfpromo`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + 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", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest0&category=selfpromo`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + 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 lock status", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest0&locked=false`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 4); + assert.strictEqual(data.page, 0); + assert.strictEqual(segments[0].UUID, "search-normal"); + assert.strictEqual(segments[1].UUID, "search-downvote"); + assert.strictEqual(segments[2].UUID, "search-hidden"); + assert.strictEqual(segments[3].UUID, "search-shadowhidden"); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to filter by hide status", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest0&hidden=false`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 4); + assert.strictEqual(data.page, 0); + assert.strictEqual(segments[0].UUID, "search-normal"); + assert.strictEqual(segments[1].UUID, "search-downvote"); + assert.strictEqual(segments[2].UUID, "search-locked"); + assert.strictEqual(segments[3].UUID, "search-shadowhidden"); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to filter by ignored status", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest0&ignored=false`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 3); + assert.strictEqual(data.page, 0); + assert.strictEqual(segments[0].UUID, "search-normal"); + assert.strictEqual(segments[1].UUID, "search-downvote"); + assert.strictEqual(segments[2].UUID, "search-locked"); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to filter segments by min views", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest1&minViews=6`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 1); + assert.strictEqual(data.page, 0); + assert.strictEqual(segments[0].UUID, "search-highview"); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to filter segments by max views", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest1&maxViews=10`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 1); + assert.strictEqual(data.page, 0); + assert.strictEqual(segments[0].UUID, "search-lowview"); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to filter segments by min and max views", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest1&maxViews=10&minViews=1`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 1); + assert.strictEqual(data.page, 0); + assert.strictEqual(segments[0].UUID, "search-lowview"); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to filter segments by min votes", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest2&minVotes=0`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 2); + assert.strictEqual(data.page, 0); + assert.strictEqual(segments[0].UUID, "search-zerovote"); + assert.strictEqual(segments[1].UUID, "search-highvote"); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to filter segments by max votes", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest2&maxVotes=10`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 2); + assert.strictEqual(data.page, 0); + assert.strictEqual(segments[0].UUID, "search-lowvote"); + assert.strictEqual(segments[1].UUID, "search-zerovote"); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to filter segments by both min and max votes", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest2&maxVotes=10&minVotes=0`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 1); + assert.strictEqual(data.page, 0); + assert.strictEqual(segments[0].UUID, "search-zerovote"); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to get first page of results", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest4`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 12); + assert.strictEqual(data.page, 0); + assert.strictEqual(segments[0].UUID, "search-page1-1"); + assert.strictEqual(segments[1].UUID, "search-page1-2"); + assert.strictEqual(segments[2].UUID, "search-page1-3"); + assert.strictEqual(segments[3].UUID, "search-page1-4"); + assert.strictEqual(segments[4].UUID, "search-page1-5"); + assert.strictEqual(segments[5].UUID, "search-page1-6"); + assert.strictEqual(segments[6].UUID, "search-page1-7"); + assert.strictEqual(segments[7].UUID, "search-page1-8"); + assert.strictEqual(segments[8].UUID, "search-page1-9"); + assert.strictEqual(segments[9].UUID, "search-page1-10"); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to get second page of results", (done: Done) => { + fetch(`${getbaseURL()}/api/searchSegments?videoID=searchTest4&page=1`) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await res.json(); + const segments = data.segments; + assert.strictEqual(data.segmentCount, 12); + assert.strictEqual(data.page, 1); + assert.strictEqual(segments[0].UUID, "search-page2-1"); + assert.strictEqual(segments[1].UUID, "search-page2-2"); + done(); + }) + .catch(err => done(err)); + }); +});