Merge pull request #610 from mini-bomba/warning-history

Keep a history of warnings in the public database
This commit is contained in:
Ajay Ramachandran
2025-09-19 02:15:59 -04:00
committed by GitHub
39 changed files with 1001 additions and 1238 deletions

View File

@@ -31,15 +31,15 @@ module.exports = {
},
overrides: [
{
files: ["src/**/*.ts"],
files: ["**/*.ts"],
parserOptions: {
project: ["./tsconfig.json"],
project: ["./tsconfig.eslint.json"],
},
rules: {
"@typescript-eslint/no-misused-promises": "warn",
"@typescript-eslint/no-floating-promises" : "warn"
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-floating-promises" : "error"
}
},
],

View File

@@ -56,7 +56,6 @@
]
}
],
"hoursAfterWarningExpires": 24,
"rateLimit": {
"vote": {
"windowMs": 900000,

View File

@@ -25,8 +25,6 @@
"webhooks": [],
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "preview", "music_offtopic", "poi_highlight"], // List of supported categories any other category will be rejected
"getTopUsersCacheTimeMinutes": 5, // cacheTime for getTopUsers result in minutes
"maxNumberOfActiveWarnings": 3, // Users with this number of warnings will be blocked until warnings expire
"hoursAfterWarningExpire": 24,
"rateLimit": {
"vote": {
"windowMs": 900000, // 15 minutes

View File

@@ -0,0 +1,7 @@
BEGIN TRANSACTION;
ALTER TABLE "warnings" ADD "disableTime" INTEGER NULL;
UPDATE "config" SET value = 45 WHERE key = 'version';
COMMIT;

View File

@@ -37,8 +37,6 @@ addDefaults(config, {
},
deArrowTypes: ["title", "thumbnail"],
maxTitleLength: 110,
maxNumberOfActiveWarnings: 1,
hoursAfterWarningExpires: 16300000,
adminUserID: "",
discordCompletelyIncorrectReportWebhookURL: null,
discordFirstTimeSubmissionsWebhookURL: null,

View File

@@ -3,7 +3,6 @@ import { CronJob } from "cron";
import { config as serverConfig } from "../config";
import { Logger } from "../utils/logger";
import { db } from "../databases/databases";
import { DBSegment } from "../types/segments.model";
const jobConfig = serverConfig?.crons?.downvoteSegmentArchive;
@@ -14,18 +13,18 @@ export const archiveDownvoteSegment = async (dayLimit: number, voteLimit: number
Logger.info(`DownvoteSegmentArchiveJob starts at ${timeNow}`);
try {
// insert into archive sponsorTime
await db.prepare(
"run",
`INSERT INTO "archivedSponsorTimes"
SELECT *
FROM "sponsorTimes"
WHERE "votes" < ? AND (? - "timeSubmitted") > ?`,
[
voteLimit,
timeNow,
threshold
]
) as DBSegment[];
await db.prepare(
"run",
`INSERT INTO "archivedSponsorTimes"
SELECT *
FROM "sponsorTimes"
WHERE "votes" < ? AND (? - "timeSubmitted") > ?`,
[
voteLimit,
timeNow,
threshold
]
);
} catch (err) {
Logger.error("Execption when insert segment in archivedSponsorTimes");
@@ -35,15 +34,15 @@ export const archiveDownvoteSegment = async (dayLimit: number, voteLimit: number
// remove from sponsorTime
try {
await db.prepare(
"run",
'DELETE FROM "sponsorTimes" WHERE "votes" < ? AND (? - "timeSubmitted") > ?',
[
voteLimit,
timeNow,
threshold
]
) as DBSegment[];
await db.prepare(
"run",
'DELETE FROM "sponsorTimes" WHERE "votes" < ? AND (? - "timeSubmitted") > ?',
[
voteLimit,
timeNow,
threshold
]
);
} catch (err) {
Logger.error("Execption when deleting segment in sponsorTimes");

View File

@@ -6,11 +6,14 @@ export interface QueryOption {
export interface IDatabase {
init(): Promise<void>;
prepare(type: QueryType, query: string, params?: any[], options?: QueryOption): Promise<any | any[] | void>;
prepare(type: "run", query: string, params?: any[], options?: QueryOption): Promise<void>;
prepare(type: "get", query: string, params?: any[], options?: QueryOption): Promise<any>;
prepare(type: "all", query: string, params?: any[], options?: QueryOption): Promise<any[]>;
prepare(type: QueryType, query: string, params?: any[], options?: QueryOption): Promise<any>;
highLoad(): boolean;
shouldUseRedisTimeout(): boolean;
}
export type QueryType = "get" | "all" | "run";
export type QueryType = "get" | "all" | "run";

View File

@@ -109,7 +109,7 @@ export class Postgres implements IDatabase {
}
}
async prepare(type: QueryType, query: string, params?: any[], options: QueryOption = {}): Promise<any[]> {
async prepare(type: QueryType, query: string, params?: any[], options: QueryOption = {}): Promise<any> {
// Convert query to use numbered parameters
let count = 1;
for (let char = 0; char < query.length; char++) {

View File

@@ -13,7 +13,7 @@ export class Sqlite implements IDatabase {
}
// eslint-disable-next-line require-await
async prepare(type: QueryType, query: string, params: any[] = []): Promise<any[]> {
async prepare(type: QueryType, query: string, params: any[] = []): Promise<any> {
// Logger.debug(`prepare (sqlite): type: ${type}, query: ${query}, params: ${params}`);
const preparedQuery = this.db.prepare(Sqlite.processQuery(query));

View File

@@ -122,7 +122,7 @@ function chooseSegment<T extends DBSegment>(choices: T[]): FullVideoSegmentVideo
return {
segments: transformDBSegments(choices),
hasStartSegment
}
};
}
// if locked, only choose from locked
const locked = choices.filter((segment) => segment.locked);

View File

@@ -164,20 +164,15 @@ async function autoModerateSubmission(apiVideoDetails: videoDetails,
}
async function checkUserActiveWarning(userID: HashedUserID): Promise<CheckResult> {
const MILLISECONDS_IN_HOUR = 3600000;
const now = Date.now();
const warnings = (await db.prepare("all",
const warning = await db.prepare("get",
`SELECT "reason"
FROM warnings
WHERE "userID" = ? AND "issueTime" > ? AND enabled = 1 AND type = 0
WHERE "userID" = ? AND enabled = 1 AND type = 0
ORDER BY "issueTime" DESC`,
[
userID,
Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))
],
) as {reason: string}[]).sort((a, b) => (b?.reason?.length ?? 0) - (a?.reason?.length ?? 0));
[userID],
) as {reason: string};
if (warnings?.length >= config.maxNumberOfActiveWarnings) {
if (warning != null) {
const defaultMessage = "Submission rejected due to a tip from a moderator. This means that we noticed you were making some common mistakes"
+ " that are not malicious, and we just want to clarify the rules. "
+ "Could you please send a message in discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app so we can further help you? "
@@ -185,7 +180,7 @@ async function checkUserActiveWarning(userID: HashedUserID): Promise<CheckResult
return {
pass: false,
errorMessage: defaultMessage + (warnings[0]?.reason?.length > 0 ? `\n\nTip message: '${warnings[0].reason}'` : ""),
errorMessage: defaultMessage + (warning.reason?.length > 0 ? `\n\nTip message: '${warning.reason}'` : ""),
errorCode: 403
};
}

View File

@@ -4,7 +4,6 @@ import { db } from "../databases/databases";
import { isUserVIP } from "../utils/isUserVIP";
import { getHashCache } from "../utils/getHashCache";
import { HashedUserID, UserID } from "../types/user.model";
import { config } from "../config";
import { generateWarningDiscord, warningData, dispatchEvent } from "../utils/webhookUtils";
import { WarningType } from "../types/warning.model";
@@ -16,12 +15,7 @@ type warningEntry = {
reason: string
}
function checkExpiredWarning(warning: warningEntry): boolean {
const MILLISECONDS_IN_HOUR = 3600000;
const now = Date.now();
const expiry = Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR));
return warning.issueTime > expiry && !warning.enabled;
}
const MAX_EDIT_DELAY = 900000; // 15 mins
const getUsername = (userID: HashedUserID) => db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [userID], { useReplica: true });
@@ -44,30 +38,30 @@ export async function postWarning(req: Request, res: Response): Promise<Response
try {
if (enabled) {
const previousWarning = await db.prepare("get", 'SELECT * FROM "warnings" WHERE "userID" = ? AND "issuerUserID" = ? AND "type" = ?', [userID, issuerUserID, type]) as warningEntry;
if (!reason) {
return res.status(400).json({ "message": "Missing warning reason" });
}
const previousWarning = await db.prepare("get", 'SELECT * FROM "warnings" WHERE "userID" = ? AND "type" = ? AND "enabled" = 1', [userID, type]) as warningEntry;
if (!previousWarning) {
if (!reason) {
return res.status(400).json({ "message": "Missing warning reason" });
}
await db.prepare(
"run",
'INSERT INTO "warnings" ("userID", "issueTime", "issuerUserID", "enabled", "reason", "type") VALUES (?, ?, ?, 1, ?, ?)',
[userID, issueTime, issuerUserID, reason, type]
);
resultStatus = "issued to";
// check if warning is still within issue time and warning is not enabled
} else if (checkExpiredWarning(previousWarning) ) {
// allow a warning to be edited by the same vip within 15 mins of issuing
} else if (issuerUserID === previousWarning.issuerUserID && (Date.now() - MAX_EDIT_DELAY) < previousWarning.issueTime) {
await db.prepare(
"run", 'UPDATE "warnings" SET "enabled" = 1, "reason" = ? WHERE "userID" = ? AND "issueTime" = ?',
"run", 'UPDATE "warnings" SET "reason" = ? WHERE "userID" = ? AND "issueTime" = ?',
[reason, userID, previousWarning.issueTime]
);
resultStatus = "re-enabled for";
resultStatus = "edited for";
} else {
return res.sendStatus(409);
}
} else {
await db.prepare("run", 'UPDATE "warnings" SET "enabled" = 0 WHERE "userID" = ? AND "type" = ?', [userID, type]);
await db.prepare("run", 'UPDATE "warnings" SET "enabled" = 0, "disableTime" = ? WHERE "userID" = ? AND "type" = ? AND "enabled" = 1', [issueTime, userID, type]);
resultStatus = "removed from";
}

View File

@@ -7,7 +7,7 @@ import { isUserBanned } from "../utils/checkBan";
import { HashedUserID } from "../types/user.model";
import { isRequestInvalid } from "../utils/requestValidator";
function logUserNameChange(userID: string, newUserName: string, oldUserName: string, updatedByAdmin: boolean): Promise<Response> {
function logUserNameChange(userID: string, newUserName: string, oldUserName: string, updatedByAdmin: boolean): Promise<void> {
return privateDB.prepare("run",
`INSERT INTO "userNameLogs"("userID", "newUserName", "oldUserName", "updatedByAdmin", "updatedAt") VALUES(?, ?, ?, ?, ?)`,
[userID, newUserName, oldUserName, + updatedByAdmin, new Date().getTime()]

View File

@@ -379,14 +379,12 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID
return { status: 400 };
}
const MILLISECONDS_IN_HOUR = 3600000;
const now = Date.now();
const warnings = (await db.prepare("all", `SELECT "reason" FROM warnings WHERE "userID" = ? AND "issueTime" > ? AND enabled = 1 AND type = 0`,
[nonAnonUserID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))],
const warning = (await db.prepare("get", `SELECT "reason" FROM warnings WHERE "userID" = ? AND enabled = 1 AND type = 0`,
[nonAnonUserID],
));
if (warnings.length >= config.maxNumberOfActiveWarnings) {
const warningReason = warnings[0]?.reason;
if (warning != null) {
const warningReason = warning.reason;
lock.unlock();
return { status: 403, message: "Vote rejected due to a tip from a moderator. This means that we noticed you were making some common mistakes that are not malicious, and we just want to clarify the rules. " +
"Could you please send a message in Discord or Matrix so we can further help you?" +

View File

@@ -109,8 +109,6 @@ export interface SBSConfig {
categorySupport: Record<string, string[]>;
maxTitleLength: number;
getTopUsersCacheTimeMinutes: number;
maxNumberOfActiveWarnings: number;
hoursAfterWarningExpires: number;
rateLimit: {
vote: RateLimitConfig;
view: RateLimitConfig;

View File

@@ -50,7 +50,6 @@
]
}
],
"hoursAfterWarningExpires": 24,
"rateLimit": {
"vote": {
"windowMs": 900000,

View File

@@ -73,11 +73,11 @@ describe("getUserID", () => {
)
);
it("Should be able to get multiple fuzzy user info from middle", () => {
it("Should be able to get multiple fuzzy user info from middle", () =>
validateSearch("user",
[users["fuzzy_1"], users["fuzzy_2"], users["specific_1"]]
);
});
)
);
it("Should be able to get with fuzzy public ID", () => {
const userID = users["public_1"].pubID.substring(0,60);
@@ -117,4 +117,4 @@ describe("getUserID 400/ 404", () => {
it("Should not allow usernames less than 3 characters", () => validateStatus("aa", 400));
it("Should return 404 if escaped backslashes present", () => validateStatus("%redos\\\\_", 404));
it("Should return 404 if backslashes present", () => validateStatus(`\\%redos\\_`, 404));
});
});

View File

@@ -81,19 +81,19 @@ describe("getUserInfo", () => {
// warnings & bans
// warn-0
insertWarning(db, users["warn-0"].pubID, { reason: "warning0-0", issueTime: 10 });
await insertWarning(db, users["warn-0"].pubID, { reason: "warning0-0", issueTime: 10 });
// warn-1
insertWarning(db, users["warn-1"].pubID, { reason: "warning1-0", issueTime: 20 });
insertWarning(db, users["warn-1"].pubID, { reason: "warning1-1", issueTime: 30 });
await insertWarning(db, users["warn-1"].pubID, { reason: "warning1-0", issueTime: 20 });
await insertWarning(db, users["warn-1"].pubID, { reason: "warning1-1", issueTime: 30 });
// warn -2
insertWarning(db, users["warn-2"].pubID, { reason: "warning2-0", issueTime: 40, enabled: false });
await insertWarning(db, users["warn-2"].pubID, { reason: "warning2-0", issueTime: 40, enabled: false });
// warn-3
insertWarning(db, users["warn-3"].pubID, { reason: "warning3-0", issueTime: 50 });
insertWarning(db, users["warn-3"].pubID, { reason: "warning3-1", issueTime: 60, enabled: false });
await insertWarning(db, users["warn-3"].pubID, { reason: "warning3-0", issueTime: 50 });
await insertWarning(db, users["warn-3"].pubID, { reason: "warning3-1", issueTime: 60, enabled: false });
// ban-
insertBan(db, users["ban-1"].pubID);
insertBan(db, users["ban-2"].pubID);
await insertBan(db, users["ban-1"].pubID);
await insertBan(db, users["ban-2"].pubID);
});
it("Should be able to get a 200", () => statusTest(200, { userID: users["n-1"].privID }));

View File

@@ -33,11 +33,11 @@ const checkUserViews = (user: User) =>
});
describe("getViewsForUser", function() {
before(() => {
before(async () => {
// add views for users
insertSegment(db, { userID: users["u-1"].pubID, views: users["u-1"].info.views1 });
insertSegment(db, { userID: users["u-1"].pubID, views: users["u-1"].info.views2 });
insertSegment(db, { userID: users["u-2"].pubID, views: users["u-2"].info.views });
await insertSegment(db, { userID: users["u-1"].pubID, views: users["u-1"].info.views1 });
await insertSegment(db, { userID: users["u-1"].pubID, views: users["u-1"].info.views2 });
await insertSegment(db, { userID: users["u-2"].pubID, views: users["u-2"].info.views });
});
it("Should get back views for user one", () =>
checkUserViews(users["u-1"])
@@ -53,4 +53,4 @@ describe("getViewsForUser", function() {
client({ url: endpoint })
.then(res => assert.strictEqual(res.status, 400))
);
});
});

View File

@@ -15,7 +15,6 @@ describe("postBranding", () => {
const userID5 = `PostBrandingUser5${".".repeat(16)}`;
const userID6 = `PostBrandingUser6${".".repeat(16)}`;
const userID7 = `PostBrandingUser7${".".repeat(16)}`;
const userID8 = `PostBrandingUser8${".".repeat(16)}`;
const bannedUser = `BannedPostBrandingUser${".".repeat(16)}`;

View File

@@ -50,12 +50,12 @@ describe("postSkipSegments", () => {
const queryDatabaseActionType = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "actionType" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
const queryDatabaseVideoInfo = (videoID: string) => db.prepare("get", `SELECT * FROM "videoInfo" WHERE "videoID" = ?`, [videoID]);
before(() => {
before(async () => {
const insertSponsorTimeQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", views, category, "actionType", "videoDuration", "shadowHidden", "hashedVideoID") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
db.prepare("run", insertSponsorTimeQuery, ["full_video_segment", 0, 0, 0, "full-video-uuid-0", submitUserTwoHash, 0, 0, "sponsor", "full", 0, 0, "full_video_segment"]);
await db.prepare("run", insertSponsorTimeQuery, ["full_video_segment", 0, 0, 0, "full-video-uuid-0", submitUserTwoHash, 0, 0, "sponsor", "full", 0, 0, "full_video_segment"]);
const insertVipUserQuery = 'INSERT INTO "vipUsers" ("userID") VALUES (?)';
db.prepare("run", insertVipUserQuery, [getHash(submitVIPuser)]);
await db.prepare("run", insertVipUserQuery, [getHash(submitVIPuser)]);
});
it("Should be able to submit a single time (Params method)", (done) => {

View File

@@ -19,11 +19,11 @@ describe("postSkipSegments - Automod 80%", () => {
const queryDatabaseCategory = (videoID: string) => db.prepare("all", `SELECT "startTime", "endTime", "category" FROM "sponsorTimes" WHERE "videoID" = ? and "votes" > -1`, [videoID]);
before(() => {
before(async () => {
const insertSponsorTimeQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", views, category, "actionType", "videoDuration", "shadowHidden", "hashedVideoID") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
db.prepare("run", insertSponsorTimeQuery, [over80VideoID, 0, 1000, 0, "80percent-uuid-0", userIDHash, 0, 0, "interaction", "skip", 0, 0, over80VideoID]);
db.prepare("run", insertSponsorTimeQuery, [over80VideoID, 1001, 1005, 0, "80percent-uuid-1", userIDHash, 0, 0, "interaction", "skip", 0, 0, over80VideoID]);
db.prepare("run", insertSponsorTimeQuery, [over80VideoID, 0, 5000, -2, "80percent-uuid-2", userIDHash, 0, 0, "interaction", "skip", 0, 0, over80VideoID]);
await db.prepare("run", insertSponsorTimeQuery, [over80VideoID, 0, 1000, 0, "80percent-uuid-0", userIDHash, 0, 0, "interaction", "skip", 0, 0, over80VideoID]);
await db.prepare("run", insertSponsorTimeQuery, [over80VideoID, 1001, 1005, 0, "80percent-uuid-1", userIDHash, 0, 0, "interaction", "skip", 0, 0, over80VideoID]);
await db.prepare("run", insertSponsorTimeQuery, [over80VideoID, 0, 5000, -2, "80percent-uuid-2", userIDHash, 0, 0, "interaction", "skip", 0, 0, over80VideoID]);
});
it("Should allow multiple times if total is under 80% of video (JSON method)", (done) => {

View File

@@ -21,10 +21,10 @@ describe("postSkipSegments - duration", () => {
const queryDatabaseDuration = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "videoDuration" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
before(() => {
before(async () => {
const insertSponsorTimeQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", views, category, "actionType", "videoDuration", "shadowHidden", "hashedVideoID") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
db.prepare("run", insertSponsorTimeQuery, ["full_video_duration_segment", 0, 0, 0, "full-video-duration-uuid-0", userIDTwo, 0, 0, "sponsor", "full", 123, 0, "full_video_duration_segment"]);
db.prepare("run", insertSponsorTimeQuery, ["full_video_duration_segment", 25, 30, 0, "full-video-duration-uuid-1", userIDTwo, 0, 0, "sponsor", "skip", 123, 0, "full_video_duration_segment"]);
await db.prepare("run", insertSponsorTimeQuery, ["full_video_duration_segment", 0, 0, 0, "full-video-duration-uuid-0", userIDTwo, 0, 0, "sponsor", "full", 123, 0, "full_video_duration_segment"]);
await db.prepare("run", insertSponsorTimeQuery, ["full_video_duration_segment", 25, 30, 0, "full-video-duration-uuid-1", userIDTwo, 0, 0, "sponsor", "skip", 123, 0, "full_video_duration_segment"]);
});
it("Should be able to submit a single time with a precise duration close to the one from the YouTube API (JSON method)", (done) => {
@@ -202,4 +202,4 @@ describe("postSkipSegments - duration", () => {
})
.catch(err => done(err));
});
});
});

View File

@@ -21,17 +21,17 @@ describe("postSkipSegments Features - Chapters", () => {
};
}
before(() => {
before(async () => {
const submitNumberOfTimes = 10;
const submitUser_reputationHash = getHash(submitUser_reputation);
const insertSponsorTimeQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", views, category, "actionType", "shadowHidden") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
for (let i = 0; i < submitNumberOfTimes; i++) {
const uuid = `post_reputation_uuid-${i}`;
const videoID = `post_reputation_video-${i}`;
db.prepare("run", insertSponsorTimeQuery, [videoID, 1, 11, 5, 1, uuid, submitUser_reputationHash, 1597240000000, 50, "sponsor", "skip", 0]);
await db.prepare("run", insertSponsorTimeQuery, [videoID, 1, 11, 5, 1, uuid, submitUser_reputationHash, 1597240000000, 50, "sponsor", "skip", 0]);
}
// user feature
db.prepare("run", `INSERT INTO "userFeatures" ("userID", "feature", "issuerUserID", "timeSubmitted") VALUES(?, ?, ?, ?)`, [getHash(submitUser_feature), Feature.ChapterSubmitter, "generic-VIP", 0]);
await db.prepare("run", `INSERT INTO "userFeatures" ("userID", "feature", "issuerUserID", "timeSubmitted") VALUES(?, ?, ?, ?)`, [getHash(submitUser_feature), Feature.ChapterSubmitter, "generic-VIP", 0]);
});
it("Should be able to submit a single chapter due to reputation (JSON method)", (done) => {

View File

@@ -9,10 +9,10 @@ describe("postSkipSegments - LockedVideos", () => {
const videoID = "lockedVideo";
const userID = userIDOne;
before(() => {
before(async () => {
const insertLockCategoriesQuery = `INSERT INTO "lockCategories" ("userID", "videoID", "category", "reason") VALUES(?, ?, ?, ?)`;
db.prepare("run", insertLockCategoriesQuery, [getHash(VIPLockUser), videoID, "sponsor", "Custom Reason"]);
db.prepare("run", insertLockCategoriesQuery, [getHash(VIPLockUser), videoID, "intro", ""]);
await db.prepare("run", insertLockCategoriesQuery, [getHash(VIPLockUser), videoID, "sponsor", "Custom Reason"]);
await db.prepare("run", insertLockCategoriesQuery, [getHash(VIPLockUser), videoID, "intro", ""]);
});
it("Should return 403 and custom reason for submiting in lockedCategory", (done) => {
@@ -67,4 +67,4 @@ describe("postSkipSegments - LockedVideos", () => {
})
.catch(err => done(err));
});
});
});

View File

@@ -19,8 +19,8 @@ describe("postSkipSegments - shadowban", () => {
const queryDatabaseShadowhidden = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "shadowHidden", "userID" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
before(() => {
db.prepare("run", `INSERT INTO "shadowBannedUsers" ("userID") VALUES(?)`, [banUser01Hash]);
before(async () => {
await db.prepare("run", `INSERT INTO "shadowBannedUsers" ("userID") VALUES(?)`, [banUser01Hash]);
});
it("Should automatically shadowban segments if user is banned", (done) => {
@@ -65,4 +65,4 @@ describe("postSkipSegments - shadowban", () => {
})
.catch(err => done(err));
});
});
});

View File

@@ -21,10 +21,10 @@ describe("postSkipSegments - userAgent", () => {
};
const dbFormatSegment = convertSingleToDBFormat(segment);
before(() => {
before(async () => {
const insertLockCategoriesQuery = `INSERT INTO "lockCategories" ("userID", "videoID", "category", "reason") VALUES(?, ?, ?, ?)`;
db.prepare("run", insertLockCategoriesQuery, [getHash(VIPLockUser), videoID, "sponsor", "Custom Reason"]);
db.prepare("run", insertLockCategoriesQuery, [getHash(VIPLockUser), videoID, "intro", ""]);
await db.prepare("run", insertLockCategoriesQuery, [getHash(VIPLockUser), videoID, "sponsor", "Custom Reason"]);
await db.prepare("run", insertLockCategoriesQuery, [getHash(VIPLockUser), videoID, "intro", ""]);
});
it("Should be able to submit with empty user-agent", (done) => {
@@ -101,4 +101,4 @@ describe("postSkipSegments - userAgent", () => {
})
.catch(err => done(err));
});
});
});

View File

@@ -1,20 +1,11 @@
import { config } from "../../src/config";
import { getHash } from "../../src/utils/getHash";
import { db } from "../../src/databases/databases";
import assert from "assert";
import { client } from "../utils/httpClient";
import { usersForSuite } from "../utils/randomUsers";
describe("postSkipSegments Warnings", () => {
// Constant and helpers
const warnUser01 = "warn-user01";
const warnUser01Hash = getHash(warnUser01);
const warnUser02 = "warn-user02";
const warnUser02Hash = getHash(warnUser02);
const warnUser03 = "warn-user03";
const warnUser03Hash = getHash(warnUser03);
const warnUser04 = "warn-user04";
const warnUser04Hash = getHash(warnUser04);
const users = usersForSuite("postSkipSegmentsWarnings");
const warnVideoID = "postSkipSegments-warn-video";
const endpoint = "/api/skipSegments";
@@ -24,104 +15,92 @@ describe("postSkipSegments Warnings", () => {
data
});
before(() => {
before(async () => {
const now = Date.now();
const warnVip01Hash = getHash("postSkipSegmentsWarnVIP");
const reason01 = "Reason01";
const reason02 = "";
const reason03 = "Reason03";
const MILLISECONDS_IN_HOUR = 3600000;
const WARNING_EXPIRATION_TIME = config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR;
const insertWarningQuery = 'INSERT INTO warnings ("userID", "issuerUserID", "enabled", "reason", "issueTime") VALUES(?, ?, ?, ?, ?)';
// User 1 | 1 active | custom reason
db.prepare("run", insertWarningQuery, [warnUser01Hash, warnVip01Hash, 1, reason01, now]);
await db.prepare("run", insertWarningQuery, [users.u01.public, users.vip01.public, 1, reason01, now]);
// User 2 | 1 inactive | default reason
db.prepare("run", insertWarningQuery, [warnUser02Hash, warnVip01Hash, 0, reason02, now]);
// User 3 | 1 expired, active | custom reason
db.prepare("run", insertWarningQuery, [warnUser03Hash, warnVip01Hash, 1, reason03, (now - WARNING_EXPIRATION_TIME - 1000)]);
await db.prepare("run", insertWarningQuery, [users.u02.public, users.vip01.public, 0, reason02, now]);
// User 3 | 1 inactive, 1 active | different reasons
await db.prepare("run", insertWarningQuery, [users.u03.public, users.vip01.public, 0, reason01, now]);
await db.prepare("run", insertWarningQuery, [users.u03.public, users.vip01.public, 1, reason03, now+1]);
// User 4 | 1 active | default reason
db.prepare("run", insertWarningQuery, [warnUser04Hash, warnVip01Hash, 1, reason02, now]);
await db.prepare("run", insertWarningQuery, [users.u04.public, users.vip01.public, 1, reason02, now]);
});
it("Should be rejected with custom message if user has active warnings", (done) => {
postSkipSegmentJSON({
userID: warnUser01,
it("Should be rejected with custom message if user has active warnings", async () => {
const res = await postSkipSegmentJSON({
userID: users.u01.private,
videoID: warnVideoID,
segments: [{
segment: [0, 10],
category: "sponsor",
}],
})
.then(res => {
assert.strictEqual(res.status, 403);
const errorMessage = res.data;
const reason = "Reason01";
const expected = "Submission rejected due to a tip from a moderator. This means that we noticed you were making some common mistakes"
+ " that are not malicious, and we just want to clarify the rules. "
+ "Could you please send a message in discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app so we can further help you? "
+ `Your userID is ${warnUser01Hash}.\n\nTip message: '${reason}'`;
});
assert.strictEqual(res.status, 403);
const errorMessage = res.data;
const reason = "Reason01";
const expected = "Submission rejected due to a tip from a moderator. This means that we noticed you were making some common mistakes"
+ " that are not malicious, and we just want to clarify the rules. "
+ "Could you please send a message in discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app so we can further help you? "
+ `Your userID is ${users.u01.public}.\n\nTip message: '${reason}'`;
assert.strictEqual(errorMessage, expected);
done();
})
.catch(err => done(err));
assert.strictEqual(errorMessage, expected);
});
it("Should be accepted if user has inactive warning", (done) => {
postSkipSegmentJSON({
userID: warnUser02,
it("Should be accepted if user has inactive warning", async () => {
const res = await postSkipSegmentJSON({
userID: users.u02.private,
videoID: warnVideoID,
segments: [{
segment: [50, 60],
category: "sponsor",
}],
})
.then(res => {
assert.ok(res.status === 200, `Status code was ${res.status} ${res.data}`);
done();
})
.catch(err => done(err));
});
assert.ok(res.status === 200, `Status code was ${res.status} ${res.data}`);
});
it("Should be accepted if user has expired warning", (done) => {
postSkipSegmentJSON({
userID: warnUser03,
it("Should be rejected with custom message if user has active warnings, even if has one inactive warning, should use current message", async () => {
const res = await postSkipSegmentJSON({
userID: users.u03.private,
videoID: warnVideoID,
segments: [{
segment: [53, 60],
segment: [10, 20],
category: "sponsor",
}],
})
.then(res => {
assert.ok(res.status === 200, `Status code was ${res.status} ${res.data}`);
done();
})
.catch(err => done(err));
});
assert.strictEqual(res.status, 403);
const errorMessage = res.data;
const reason = "Reason03";
const expected = "Submission rejected due to a tip from a moderator. This means that we noticed you were making some common mistakes"
+ " that are not malicious, and we just want to clarify the rules. "
+ "Could you please send a message in discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app so we can further help you? "
+ `Your userID is ${users.u03.public}.\n\nTip message: '${reason}'`;
assert.strictEqual(errorMessage, expected);
});
it("Should be rejected with default message if user has active warning", (done) => {
postSkipSegmentJSON({
userID: warnUser04,
it("Should be rejected with default message if user has active warning", async () => {
const res = await postSkipSegmentJSON({
userID: users.u04.private,
videoID: warnVideoID,
segments: [{
segment: [0, 10],
category: "sponsor",
}],
})
.then(res => {
assert.strictEqual(res.status, 403);
const errorMessage = res.data;
const expected = "Submission rejected due to a tip from a moderator. This means that we noticed you were making some common mistakes"
+ " that are not malicious, and we just want to clarify the rules. "
+ "Could you please send a message in discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app so we can further help you? "
+ `Your userID is ${warnUser04Hash}.`;
assert.strictEqual(errorMessage, expected);
done();
})
.catch(err => done(err));
});
assert.strictEqual(res.status, 403);
const errorMessage = res.data;
const expected = "Submission rejected due to a tip from a moderator. This means that we noticed you were making some common mistakes"
+ " that are not malicious, and we just want to clarify the rules. "
+ "Could you please send a message in discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app so we can further help you? "
+ `Your userID is ${users.u04.public}.`;
assert.strictEqual(errorMessage, expected);
});
});

View File

@@ -3,199 +3,329 @@ import { db } from "../../src/databases/databases";
import { getHash } from "../../src/utils/getHash";
import assert from "assert";
import { client } from "../utils/httpClient";
import { usersForSuite } from "../utils/randomUsers";
describe("postWarning", () => {
// constants
const endpoint = "/api/warnUser";
const getWarning = (userID: string, type = 0) => db.prepare("get", `SELECT "userID", "issueTime", "issuerUserID", enabled, "reason" FROM warnings WHERE "userID" = ? AND "type" = ?`, [userID, type]);
const getWarning = (userID: string, type = 0) => db.prepare("all", `SELECT * FROM warnings WHERE "userID" = ? AND "type" = ? ORDER BY "issueTime" ASC`, [userID, type]);
const warneduserOneID = "warning-0";
const warnedUserTwoID = "warning-1";
const warnedUserOnePublicID = getHash(warneduserOneID);
const warnedUserTwoPublicID = getHash(warnedUserTwoID);
const warningVipOne = "warning-vip-1";
const warningVipTwo = "warning-vip-2";
const nonVipUser = "warning-non-vip";
const users = usersForSuite("postWarning");
before(async () => {
await db.prepare("run", `INSERT INTO "vipUsers" ("userID") VALUES (?)`, [getHash(warningVipOne)]);
await db.prepare("run", `INSERT INTO "vipUsers" ("userID") VALUES (?)`, [getHash(warningVipTwo)]);
const insertWarningQuery = 'INSERT INTO warnings ("userID", "issuerUserID", "enabled", "reason", "issueTime") VALUES(?, ?, ?, ?, ?)';
const insertWarningQueryWithDisableTime = 'INSERT INTO warnings ("userID", "issuerUserID", "enabled", "reason", "issueTime", "disableTime") VALUES(?, ?, ?, ?, ?, ?)';
const HOUR = 60 * 60 * 1000;
await db.prepare("run", `INSERT INTO "vipUsers" ("userID") VALUES (?)`, [users.vip1.public]);
await db.prepare("run", `INSERT INTO "vipUsers" ("userID") VALUES (?)`, [users.vip2.public]);
await db.prepare("run", insertWarningQuery, [users.u1.public, users.vip1.public, 1, "warn reason 1", (Date.now() - 24 * HOUR)]); // 24 hours is much past the edit deadline
await db.prepare("run", insertWarningQuery, [users.u2.public, users.vip1.public, 1, "warn reason 2", (Date.now() - 24 * HOUR)]);
await db.prepare("run", insertWarningQuery, [users.u3.public, users.vip1.public, 1, "warn reason 3", Date.now()]);
await db.prepare("run", insertWarningQuery, [users.u4.public, users.vip1.public, 1, "warn reason 4", Date.now()]);
await db.prepare("run", insertWarningQuery, [users.u6.public, users.vip1.public, 1, "warn reason 6", Date.now()]);
await db.prepare("run", insertWarningQuery, [users.u9.public, users.vip1.public, 0, "warn reason 9", Date.now()]);
await db.prepare("run", insertWarningQuery, [users.u10.public, users.vip1.public, 1, "warn reason 10", Date.now()]);
await db.prepare("run", insertWarningQuery, [users.u11.public, users.vip1.public, 1, "warn reason 11", Date.now()]);
await db.prepare("run", insertWarningQuery, [users.u12.public, users.vip1.public, 0, "warn reason 12", Date.now()]);
await db.prepare("run", insertWarningQuery, [users.u13.public, users.vip1.public, 0, "warn reason 13", Date.now()]);
await db.prepare("run", insertWarningQueryWithDisableTime, [users.u14.public, users.vip1.public, 0, "warn reason 14", 123, 12345]);
await db.prepare("run", insertWarningQuery, [users.u14.public, users.vip1.public, 1, "another reason 14", Date.now()]);
});
it("Should be able to create warning if vip (exp 200)", (done) => {
it("Should be able to create warning if vip (exp 200)", async () => {
const json = {
issuerUserID: warningVipOne,
userID: warnedUserOnePublicID,
issuerUserID: users.vip1.private,
userID: users.u0.public,
reason: "warning-reason-0"
};
client.post(endpoint, json)
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getWarning(json.userID);
const expected = {
enabled: 1,
issuerUserID: getHash(json.issuerUserID),
reason: json.reason,
};
assert.ok(partialDeepEquals(row, expected));
done();
})
.catch(err => done(err));
const res = await client.post(endpoint, json);
assert.strictEqual(res.status, 200);
const row = await getWarning(json.userID);
const expected = {
enabled: 1,
issuerUserID: getHash(json.issuerUserID),
reason: json.reason,
};
assert.equal(row.length, 1);
assert.ok(partialDeepEquals(row[0], expected));
});
it("Should be not be able to create a duplicate warning if vip", (done) => {
it("Should be not be able to edit a warning if past deadline and same vip", async () => {
const json = {
issuerUserID: warningVipOne,
userID: warnedUserOnePublicID,
issuerUserID: users.vip1.private,
userID: users.u1.public,
reason: "edited reason 1",
};
client.post(endpoint, json)
.then(async res => {
assert.strictEqual(res.status, 409);
const row = await getWarning(json.userID);
const expected = {
enabled: 1,
issuerUserID: getHash(json.issuerUserID),
};
assert.ok(partialDeepEquals(row, expected));
done();
})
.catch(err => done(err));
const res = await client.post(endpoint, json);
assert.strictEqual(res.status, 409);
const row = await getWarning(json.userID);
const expected = {
enabled: 1,
issuerUserID: users.vip1.public,
};
assert.equal(row.length, 1);
assert.ok(partialDeepEquals(row[0], expected));
});
it("Should be able to remove warning if vip", (done) => {
it("Should be not be able to edit a warning if past deadline and different vip", async () => {
const json = {
issuerUserID: warningVipOne,
userID: warnedUserOnePublicID,
issuerUserID: users.vip2.private,
userID: users.u2.public,
reason: "edited reason 2",
};
const res = await client.post(endpoint, json);
assert.strictEqual(res.status, 409);
const row = await getWarning(json.userID);
const expected = {
enabled: 1,
issuerUserID: users.vip1.public,
};
assert.equal(row.length, 1);
assert.ok(partialDeepEquals(row[0], expected));
});
it("Should be able to remove warning if same vip as issuer", async () => {
const json = {
issuerUserID: users.vip1.private,
userID: users.u3.public,
enabled: false
};
const beforeTime = Date.now();
client.post(endpoint, json)
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getWarning(json.userID);
const expected = {
enabled: 0
};
assert.ok(partialDeepEquals(row, expected));
done();
})
.catch(err => done(err));
});
it("Should not be able to create warning if not vip (exp 403)", (done) => {
const json = {
issuerUserID: nonVipUser,
userID: warnedUserOnePublicID,
const res = await client.post(endpoint, json);
const afterTime = Date.now();
assert.strictEqual(res.status, 200);
const row = await getWarning(json.userID);
const expected = {
enabled: 0
};
client.post(endpoint, json)
.then(res => {
assert.strictEqual(res.status, 403);
done();
})
.catch(err => done(err));
assert.equal(row.length, 1);
assert.ok(partialDeepEquals(row[0], expected));
// check disableTime
assert.ok(row[0].disableTime >= beforeTime && row[0].disableTime <= afterTime, "expected disableTime to be somewhere between execution start and end");
});
it("Should return 400 if missing body", (done) => {
client.post(endpoint, {})
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
});
it("Should re-enable disabled warning", (done) => {
it("Should be able to remove warning if not the same vip as issuer", async () => {
const json = {
issuerUserID: warningVipOne,
userID: warnedUserOnePublicID,
enabled: true
};
client.post(endpoint, json)
.then(async res => {
assert.strictEqual(res.status, 200);
const data = await getWarning(json.userID);
const expected = {
enabled: 1
};
assert.ok(partialDeepEquals(data, expected));
done();
})
.catch(err => done(err));
});
it("Should be able to remove your own warning", (done) => {
const json = {
userID: warneduserOneID,
issuerUserID: users.vip2.private,
userID: users.u4.public,
enabled: false
};
const beforeTime = Date.now();
client.post(endpoint, json)
.then(async res => {
assert.strictEqual(res.status, 200);
const data = await getWarning(warnedUserOnePublicID);
const expected = {
enabled: 0
};
assert.ok(partialDeepEquals(data, expected));
done();
})
.catch(err => done(err));
const res = await client.post(endpoint, json);
const afterTime = Date.now();
assert.strictEqual(res.status, 200);
const row = await getWarning(json.userID);
const expected = {
enabled: 0
};
assert.equal(row.length, 1);
assert.ok(partialDeepEquals(row[0], expected));
// check disableTime
assert.ok(row[0].disableTime >= beforeTime && row[0].disableTime <= afterTime, "expected disableTime to be somewhere between execution start and end");
});
it("Should not be able to add your own warning", (done) => {
it("Should not be able to create warning if not vip (exp 403)", async () => {
const json = {
userID: warneduserOneID,
issuerUserID: users.nonvip.private,
userID: users.u5.public,
reason: "warn reason 5",
};
const res = await client.post(endpoint, json);
assert.strictEqual(res.status, 403);
});
it("Should return 400 if missing body", async () => {
const res = await client.post(endpoint, {});
assert.strictEqual(res.status, 400);
});
it("Should be able to remove your own warning", async () => {
const json = {
userID: users.u6.private,
enabled: false
};
const beforeTime = Date.now();
const res = await client.post(endpoint, json);
const afterTime = Date.now();
assert.strictEqual(res.status, 200);
const row = await getWarning(users.u6.public);
const expected = {
enabled: 0
};
assert.equal(row.length, 1);
assert.ok(partialDeepEquals(row[0], expected));
// check disableTime
assert.ok(row[0].disableTime >= beforeTime && row[0].disableTime <= afterTime, "expected disableTime to be somewhere between execution start and end");
});
it("Should not be able to add your own warning", async () => {
const json = {
userID: users.u7.private,
enabled: true,
reason: "warn reason 7",
};
const res = await client.post(endpoint, json);
assert.strictEqual(res.status, 403);
const data = await getWarning(users.u7.public);
assert.equal(data.length, 0);
});
it("Should not be able to warn a user without reason", async () => {
const json = {
issuerUserID: users.vip1.private,
userID: users.u8.public,
enabled: true
};
client.post(endpoint, json)
.then(async res => {
assert.strictEqual(res.status, 403);
const data = await getWarning(warnedUserOnePublicID);
const expected = {
enabled: 0
};
assert.ok(partialDeepEquals(data, expected));
done();
})
.catch(err => done(err));
const res = await client.post(endpoint, json);
assert.strictEqual(res.status, 400);
});
it("Should not be able to warn a user without reason", (done) => {
it("Should not be able to re-warn a user without reason", async () => {
const json = {
issuerUserID: warningVipOne,
userID: warnedUserTwoPublicID,
issuerUserID: users.vip1.private,
userID: users.u9.public,
enabled: true
};
client.post(endpoint, json)
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
const res = await client.post(endpoint, json);
assert.strictEqual(res.status, 400);
});
it("Should be able to re-warn a user without reason", (done) => {
it("Should be able to edit a warning if within deadline and same vip", async () => {
const json = {
issuerUserID: warningVipOne,
userID: warnedUserOnePublicID,
enabled: true
issuerUserID: users.vip1.private,
userID: users.u10.public,
enabled: true,
reason: "edited reason 10",
};
client.post(endpoint, json)
.then(async res => {
assert.strictEqual(res.status, 200);
const data = await getWarning(warnedUserOnePublicID);
const expected = {
enabled: 1
};
assert.ok(partialDeepEquals(data, expected));
done();
})
.catch(err => done(err));
const res = await client.post(endpoint, json);
assert.strictEqual(res.status, 200);
const row = await getWarning(json.userID);
const expected = {
enabled: 1,
issuerUserID: users.vip1.public,
reason: json.reason,
};
assert.equal(row.length, 1);
assert.ok(partialDeepEquals(row[0], expected));
});
it("Should not be able to edit a warning if within deadline and different vip", async () => {
const json = {
issuerUserID: users.vip2.private,
userID: users.u11.public,
enabled: true,
reason: "edited reason 11",
};
const res = await client.post(endpoint, json);
assert.strictEqual(res.status, 409);
const row = await getWarning(json.userID);
const expected = {
enabled: 1,
issuerUserID: users.vip1.public,
reason: "warn reason 11",
};
assert.equal(row.length, 1);
assert.ok(partialDeepEquals(row[0], expected));
});
it("Should be able to warn a previously warned user again (same vip)", async () => {
const json = {
issuerUserID: users.vip1.private,
userID: users.u12.public,
enabled: true,
reason: "new reason 12",
};
const res = await client.post(endpoint, json);
assert.strictEqual(res.status, 200);
const row = await getWarning(json.userID);
const expected = [
{
enabled: 0,
issuerUserID: users.vip1.public,
reason: "warn reason 12",
},
{
enabled: 1,
issuerUserID: users.vip1.public,
reason: "new reason 12",
}
];
assert.equal(row.length, 2);
assert.ok(partialDeepEquals(row[0], expected[0]), "warning 1");
assert.ok(partialDeepEquals(row[1], expected[1]), "warning 2");
});
it("Should be able to warn a previously warned user again (different vip)", async () => {
const json = {
issuerUserID: users.vip2.private,
userID: users.u13.public,
enabled: true,
reason: "new reason 13",
};
const res = await client.post(endpoint, json);
assert.strictEqual(res.status, 200);
const row = await getWarning(json.userID);
const expected = [
{
enabled: 0,
issuerUserID: users.vip1.public,
reason: "warn reason 13",
},
{
enabled: 1,
issuerUserID: users.vip2.public,
reason: "new reason 13",
}
];
assert.equal(row.length, 2);
assert.ok(partialDeepEquals(row[0], expected[0]), "warning 1");
assert.ok(partialDeepEquals(row[1], expected[1]), "warning 2");
});
it("Disabling a warning should only set disableTime for the active warning", async () => {
const json = {
issuerUserID: users.vip2.private,
userID: users.u14.public,
enabled: false,
};
const beforeTime = Date.now();
const res = await client.post(endpoint, json);
const afterTime = Date.now();
assert.strictEqual(res.status, 200);
const row = await getWarning(json.userID);
const expected = [
{
enabled: 0,
issuerUserID: users.vip1.public,
reason: "warn reason 14",
disableTime: 12345,
},
{
enabled: 0,
issuerUserID: users.vip1.public,
reason: "another reason 14",
}
];
assert.equal(row.length, 2);
assert.ok(partialDeepEquals(row[0], expected[0]), "warning 1");
assert.ok(partialDeepEquals(row[1], expected[1]), "warning 2");
// check disableTime
assert.ok(row[1].disableTime >= beforeTime && row[1].disableTime <= afterTime, "expected disableTime to be somewhere between execution start and end");
});
});

View File

@@ -9,44 +9,32 @@ const randKey2 = genRandom(16);
const randKey3 = genRandom();
const randValue3 = genRandom();
const redisGetCheck = (key: string, expected: string | null, done: Mocha.Done): Promise<void> =>
redis.get(key)
.then(res => {
assert.strictEqual(res, expected);
done();
}).catch(err => done(err));
describe("redis test", function() {
before(async function() {
if (!config.redis?.enabled) this.skip();
await redis.set(randKey1, randValue1);
});
it("Should get stored value", (done) => {
redisGetCheck(randKey1, randValue1, done);
it("Should get stored value", async () => {
const res = await redis.get(randKey1);
assert.strictEqual(res, randValue1);
});
it("Should not be able to get not stored value", (done) => {
redis.get(randKey2)
.then(res => {
if (res) done("Value should not be found");
done();
}).catch(err => done(err));
it("Should not be able to get not stored value", async () => {
const res = await redis.get(randKey2);
assert.strictEqual(res, null, "Value should not be found");
});
it("Should be able to delete stored value", (done) => {
redis.del(randKey1)
.catch(err => done(err))
.then(() => redisGetCheck(randKey1, null, done));
it("Should be able to delete stored value", async () => {
await redis.del(randKey1);
const res = await redis.get(randKey1);
assert.strictEqual(res, null, "Deleted key should not be found");
});
it("Should be able to set expiring value", (done) => {
redis.setEx(randKey3, 8400, randValue3)
.catch(err => done(err))
.then(() => redisGetCheck(randKey3, randValue3, done));
it("Should be able to set expiring value", async () => {
await redis.setEx(randKey3, 8400, randValue3);
const res = await redis.get(randKey3);
assert.strictEqual(res, randValue3);
});
it("Should continue when undefined value is fetched", (done) => {
it("Should continue when undefined value is fetched", async () => {
const undefkey = `undefined.${genRandom()}`;
redis.get(undefkey)
.then(result => {
assert.ok(!result); // result should be falsy
done();
});
const res = await redis.get(undefkey);
assert.strictEqual(res, null);
});
});
});

View File

@@ -56,12 +56,11 @@ function wellFormatUserName(userName: string) {
return userName.replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
}
async function testUserNameChangelog(userID: string, newUserName: string, oldUserName: string, byAdmin: boolean, done: Mocha.Done) {
async function testUserNameChangelog(userID: string, newUserName: string, oldUserName: string, byAdmin: boolean) {
const log = await getLastLogUserNameChange(userID);
assert.strictEqual(newUserName, log.newUserName);
assert.strictEqual(oldUserName, log.oldUserName);
assert.strictEqual(byAdmin, Boolean(log.updatedByAdmin));
return done();
}
const endpoint = "/api/setUsername";
@@ -111,147 +110,104 @@ describe("setUsername", () => {
}
});
it("Should be able to set username that has never been set", (done) => {
postSetUserName(user00PrivateUserID, username00)
.then(async res => {
const usernameInfo = await getUsernameInfo(getHash(user00PrivateUserID));
assert.strictEqual(res.status, 200);
assert.strictEqual(usernameInfo.userName, username00);
assert.notStrictEqual(usernameInfo.locked, 1, "username should not be locked");
done();
})
.catch((err) => done(err));
it("Should be able to set username that has never been set", async () => {
const res = await postSetUserName(user00PrivateUserID, username00);
const usernameInfo = await getUsernameInfo(getHash(user00PrivateUserID));
assert.strictEqual(res.status, 200);
assert.strictEqual(usernameInfo.userName, username00);
assert.notStrictEqual(usernameInfo.locked, 1, "username should not be locked");
});
it("Should return 200", (done) => {
it("Should return 200", async () => {
const username = "Changed%20Username";
postSetUserName(user01PrivateUserID, username)
.then(res => {
assert.strictEqual(res.status, 200);
testUserNameChangelog(user01PrivateUserID, username, username01, false, done);
})
.catch((err) => done(err));
const res = await postSetUserName(user01PrivateUserID, username);
assert.strictEqual(res.status, 200);
await testUserNameChangelog(user01PrivateUserID, username, username01, false);
});
it('Should return 400 for missing param "userID"', (done) => {
client({
it('Should return 400 for missing param "userID"', async () => {
const res = await client({
method: "POST",
url: endpoint,
data: {
userName: "MyUsername"
}
})
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch((err) => done(err));
});
assert.strictEqual(res.status, 400);
});
it('Should return 400 for missing param "username"', (done) => {
client({
it('Should return 400 for missing param "username"', async () => {
const res = await client({
method: "POST",
url: endpoint,
data: {
userID: "test"
}
})
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch((err) => done(err));
});
assert.strictEqual(res.status, 400);
});
it('Should return 400 for "username" longer then 64 characters', (done) => {
it('Should return 400 for "username" longer then 64 characters', async () => {
const username65 = "0000000000000000000000000000000000000000000000000000000000000000X";
postSetUserName("test", username65)
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch((err) => done(err));
const res = await postSetUserName("test", username65);
assert.strictEqual(res.status, 400);
});
it('Should not change username if it contains "discord"', (done) => {
it('Should not change username if it contains "discord"', async () => {
const newUsername = "discord.me";
postSetUserName(user02PrivateUserID, newUsername)
.then(async res => {
assert.strictEqual(res.status, 200);
const userNameInfo = await getUsernameInfo(getHash(user02PrivateUserID));
assert.notStrictEqual(userNameInfo.userName, newUsername);
done();
})
.catch((err) => done(err));
const res = await postSetUserName(user02PrivateUserID, newUsername);
assert.strictEqual(res.status, 200);
const userNameInfo = await getUsernameInfo(getHash(user02PrivateUserID));
assert.notStrictEqual(userNameInfo.userName, newUsername);
});
it("Should be able to change username", (done) => {
it("Should be able to change username", async () => {
const newUsername = "newUsername";
postSetUserName(user03PrivateUserID, newUsername)
.then(async () => {
const usernameInfo = await getUsernameInfo(getHash(user03PrivateUserID));
assert.strictEqual(usernameInfo.userName, newUsername, "Username should change");
assert.notStrictEqual(usernameInfo.locked, 1, "Username should not be locked");
testUserNameChangelog(user03PrivateUserID, newUsername, username03, false, done);
})
.catch((err) => done(err));
await postSetUserName(user03PrivateUserID, newUsername);
const usernameInfo = await getUsernameInfo(getHash(user03PrivateUserID));
assert.strictEqual(usernameInfo.userName, newUsername, "Username should change");
assert.notStrictEqual(usernameInfo.locked, 1, "Username should not be locked");
await testUserNameChangelog(user03PrivateUserID, newUsername, username03, false);
});
it("Should not be able to change locked username", (done) => {
it("Should not be able to change locked username", async () => {
const newUsername = "newUsername";
postSetUserName(user04PrivateUserID, newUsername)
.then(async () => {
const usernameInfo = await getUsernameInfo(getHash(user04PrivateUserID));
assert.notStrictEqual(usernameInfo.userName, newUsername, "Username should not be changed");
assert.strictEqual(usernameInfo.locked, 1, "username should be locked");
done();
})
.catch((err) => done(err));
await postSetUserName(user04PrivateUserID, newUsername);
const usernameInfo = await getUsernameInfo(getHash(user04PrivateUserID));
assert.notStrictEqual(usernameInfo.userName, newUsername, "Username should not be changed");
assert.strictEqual(usernameInfo.locked, 1, "username should be locked");
});
it("Should filter out unicode control characters", (done) => {
it("Should filter out unicode control characters", async () => {
const newUsername = "This\nUsername+has\tInvalid+Characters";
postSetUserName(user05PrivateUserID, newUsername)
.then(async () => {
const usernameInfo = await getUsernameInfo(getHash(user05PrivateUserID));
assert.notStrictEqual(usernameInfo.userName, newUsername, "Username should not contain control characters");
testUserNameChangelog(user05PrivateUserID, wellFormatUserName(newUsername), username05, false, done);
})
.catch((err) => done(err));
await postSetUserName(user05PrivateUserID, newUsername);
const usernameInfo = await getUsernameInfo(getHash(user05PrivateUserID));
assert.notStrictEqual(usernameInfo.userName, newUsername, "Username should not contain control characters");
await testUserNameChangelog(user05PrivateUserID, wellFormatUserName(newUsername), username05, false);
});
it("Incorrect adminUserID should return 403", (done) => {
it("Incorrect adminUserID should return 403", async () => {
const newUsername = "New Username";
postSetUserNameAdmin(getHash(user06PrivateUserID), newUsername,"invalidAdminID")
.then(res => {
assert.strictEqual(res.status, 403);
done();
})
.catch((err) => done(err));
const res = await postSetUserNameAdmin(getHash(user06PrivateUserID), newUsername,"invalidAdminID");
assert.strictEqual(res.status, 403);
});
it("Admin should be able to change username", (done) => {
it("Admin should be able to change username", async () => {
const newUsername = "New Username";
postSetUserNameAdmin(getHash(user06PrivateUserID), newUsername, adminPrivateUserID)
.then(async () => {
const usernameInfo = await getUsernameInfo(getHash(user06PrivateUserID));
assert.strictEqual(usernameInfo.userName, newUsername, "username should be changed");
assert.strictEqual(usernameInfo.locked, 1, "Username should be locked");
testUserNameChangelog(user06PrivateUserID, newUsername, username06, true, done);
})
.catch((err) => done(err));
await postSetUserNameAdmin(getHash(user06PrivateUserID), newUsername, adminPrivateUserID);
const usernameInfo = await getUsernameInfo(getHash(user06PrivateUserID));
assert.strictEqual(usernameInfo.userName, newUsername, "username should be changed");
assert.strictEqual(usernameInfo.locked, 1, "Username should be locked");
await testUserNameChangelog(user06PrivateUserID, newUsername, username06, true);
});
it("Admin should be able to change locked username", (done) => {
it("Admin should be able to change locked username", async () => {
const newUsername = "New Username";
postSetUserNameAdmin(getHash(user07PrivateUserID), newUsername, adminPrivateUserID)
.then(async () => {
const usernameInfo = await getUsernameInfo(getHash(user06PrivateUserID));
assert.strictEqual(usernameInfo.userName, newUsername, "Username should be changed");
assert.strictEqual(usernameInfo.locked, 1, "Username should be locked");
testUserNameChangelog(user07PrivateUserID, newUsername, username07, true, done);
})
.catch((err) => done(err));
await postSetUserNameAdmin(getHash(user07PrivateUserID), newUsername, adminPrivateUserID);
const usernameInfo = await getUsernameInfo(getHash(user06PrivateUserID));
assert.strictEqual(usernameInfo.userName, newUsername, "Username should be changed");
assert.strictEqual(usernameInfo.locked, 1, "Username should be locked");
await testUserNameChangelog(user07PrivateUserID, newUsername, username07, true);
});
it("Should delete existing username if new username is same as publicID", async () => {

View File

@@ -59,71 +59,47 @@ describe("setUsernamePrivate tests", () => {
before(() => sinon.stub(config, "minUserIDLength").value(USERID_LIMIT));
after(() => sinon.restore());
it("Existing privateID = username under Limit should retreive successfully", (done) => {
it("Existing privateID = username under Limit should retreive successfully", async () => {
const privateID = preExisting_underLimit;
hasSetUsername(getHash(privateID))
.then((usernameInfo) => {
assert.ok(usernameInfo);
done();
});
assert.ok(await hasSetUsername(getHash(privateID)));
});
it("Existing privateID = username over Limit should retreive successfully", (done) => {
it("Existing privateID = username over Limit should retreive successfully", async () => {
const privateID = preExisting_overLimit;
hasSetUsername(getHash(privateID))
.then((usernameInfo) => {
assert.ok(usernameInfo);
done();
});
assert.ok(await hasSetUsername(getHash(privateID)));
});
it("Should return error if trying to set userID = username under limit", (done) => {
it("Should return error if trying to set userID = username under limit", async () => {
const privateID = newUser_underLimit;
postSetUserName(privateID, privateID)
.then(async (res) => {
assert.strictEqual(res.status, 400);
const usernameInfo = await hasSetUsername(getHash(privateID));
assert.ok(!usernameInfo);
done();
})
.catch((err) => done(err));
const res = await postSetUserName(privateID, privateID);
assert.strictEqual(res.status, 400);
const usernameInfo = await hasSetUsername(getHash(privateID));
assert.ok(!usernameInfo);
});
it("Should return error if trying to set username = other privateID over limit", (done) => {
it("Should return error if trying to set username = other privateID over limit", async () => {
const privateID = newUser_overLimit;
postSetUserName(privateID, privateID)
.then(async (res) => {
assert.strictEqual(res.status, 400);
const usernameInfo = await hasSetUsername(getHash(privateID));
assert.ok(!usernameInfo);
done();
})
.catch((err) => done(err));
const res = await postSetUserName(privateID, privateID);
assert.strictEqual(res.status, 400);
const usernameInfo = await hasSetUsername(getHash(privateID));
assert.ok(!usernameInfo);
});
it("Should return error if trying to set username = other privateID over limit", (done) => {
it("Should return error if trying to set username = other privateID over limit", async () => {
const privateID = otherUser;
const otherUserPrivate = preExisting_overLimit;
postSetUserName(privateID, otherUserPrivate)
.then(async (res) => {
assert.strictEqual(res.status, 400);
const usernameInfo = await hasSetUsername(getHash(privateID));
assert.ok(!usernameInfo);
done();
})
.catch((err) => done(err));
const res = await postSetUserName(privateID, otherUserPrivate);
assert.strictEqual(res.status, 400);
const usernameInfo = await hasSetUsername(getHash(privateID));
assert.ok(!usernameInfo);
});
it("Should not return error if trying to set username = other privateID under limit", (done) => {
it("Should not return error if trying to set username = other privateID under limit", async () => {
const privateID = otherUser;
const otherUserPrivate = preExisting_underLimit;
postSetUserName(privateID, otherUserPrivate)
.then(async (res) => {
assert.strictEqual(res.status, 200);
const usernameInfo = await hasSetUsername(getHash(privateID));
assert.ok(usernameInfo);
done();
})
.catch((err) => done(err));
const res = await postSetUserName(privateID, otherUserPrivate);
assert.strictEqual(res.status, 200);
const usernameInfo = await hasSetUsername(getHash(privateID));
assert.ok(usernameInfo);
});
});

View File

@@ -1,4 +1,4 @@
import { db, privateDB } from "../../src/databases/databases";
import { db } from "../../src/databases/databases";
import { getHash } from "../../src/utils/getHash";
import assert from "assert";
import { Category, Service } from "../../src/types/segments.model";

View File

@@ -68,191 +68,118 @@ describe("tempVIP test", function() {
await redis.del(tempVIPKey(publicTempVIPOne));
});
it("Should update db version when starting the application", () => {
privateDB.prepare("get", "SELECT key, value FROM config where key = ?", ["version"])
.then(row => {
assert.ok(row.value >= 5, `Versions are not at least 5. private is ${row.value}`);
});
it("Should update db version when starting the application", async () => {
const row = await privateDB.prepare("get", "SELECT key, value FROM config where key = ?", ["version"]);
assert.ok(row.value >= 5, `Versions are not at least 5. private is ${row.value}`);
});
it("User should not already be temp VIP", (done) => {
checkUserVIP(publicTempVIPOne)
.then(result => {
assert.ok(!result);
})
.then(async () => {
const row = await privateDB.prepare("get", `SELECT * FROM "tempVipLog" WHERE "targetUserID" = ?`, [publicTempVIPOne]);
assert.ok(!row?.enabled);
done();
})
.catch(err => done(err));
it("User should not already be temp VIP", async () => {
assert.ok(!await checkUserVIP(publicTempVIPOne));
const row = await privateDB.prepare("get", `SELECT * FROM "tempVipLog" WHERE "targetUserID" = ?`, [publicTempVIPOne]);
assert.ok(!row?.enabled);
});
it("Should be able to normal upvote as a user", (done) => {
postVote(tempVIPOne, UUID0, 1)
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.strictEqual(row.votes, 1);
done();
})
.catch(err => done(err));
it("Should be able to normal upvote as a user", async () => {
const res = await postVote(tempVIPOne, UUID0, 1);
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.strictEqual(row.votes, 1);
});
it("Should be able to add tempVIP", (done) => {
addTempVIP("true", permVIP1, publicTempVIPOne)
.then(async res => {
assert.strictEqual(res.status, 200);
// check redis
const vip = await checkUserVIP(publicTempVIPOne);
assert.strictEqual(vip, "ChannelID");
assert.strictEqual(res.data, "Temp VIP added on channel ChannelAuthor");
// check privateDB
const row = await privateDB.prepare("get", `SELECT * FROM "tempVipLog" WHERE "targetUserID" = ?`, [publicTempVIPOne]);
assert.ok(row.enabled);
done();
})
.catch(err => done(err));
it("Should be able to add tempVIP", async () => {
const res = await addTempVIP("true", permVIP1, publicTempVIPOne);
assert.strictEqual(res.status, 200);
// check redis
const vip = await checkUserVIP(publicTempVIPOne);
assert.strictEqual(vip, "ChannelID");
assert.strictEqual(res.data, "Temp VIP added on channel ChannelAuthor");
// check privateDB
const row = await privateDB.prepare("get", `SELECT * FROM "tempVipLog" WHERE "targetUserID" = ?`, [publicTempVIPOne]);
assert.ok(row.enabled);
});
it("Should be able to VIP downvote", (done) => {
postVote(tempVIPOne, UUID0, 0)
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.strictEqual(row.votes, -2);
done();
})
.catch(err => done(err));
it("Should be able to VIP downvote", async () => {
const res = await postVote(tempVIPOne, UUID0, 0);
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.strictEqual(row.votes, -2);
});
it("Should not be able to lock segment", (done) => {
postVote(tempVIPOne, UUID0, 1)
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.strictEqual(row.votes, 1);
assert.strictEqual(row.locked, 0);
done();
})
.catch(err => done(err));
it("Should not be able to lock segment", async () => {
const res = await postVote(tempVIPOne, UUID0, 1);
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.strictEqual(row.votes, 1);
assert.strictEqual(row.locked, 0);
});
it("Should be able to change category but not lock", (done) => {
postVoteCategory(tempVIPOne, UUID0, "filler")
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.strictEqual(row.category, "filler");
assert.strictEqual(row.locked, 0);
done();
})
.catch(err => done(err));
it("Should be able to change category but not lock", async () => {
const res = await postVoteCategory(tempVIPOne, UUID0, "filler");
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.strictEqual(row.category, "filler");
assert.strictEqual(row.locked, 0);
});
it("Should be able to remove tempVIP prematurely", (done) => {
addTempVIP("false", permVIP1, publicTempVIPOne)
.then(async res => {
assert.strictEqual(res.status, 200);
const vip = await checkUserVIP(publicTempVIPOne);
assert.strictEqual(res.data, "Temp VIP removed");
assert.ok(!vip, "Should be no listed channelID");
done();
})
.catch(err => done(err));
it("Should be able to remove tempVIP prematurely", async () => {
const res = await addTempVIP("false", permVIP1, publicTempVIPOne);
assert.strictEqual(res.status, 200);
const vip = await checkUserVIP(publicTempVIPOne);
assert.strictEqual(res.data, "Temp VIP removed");
assert.ok(!vip, "Should be no listed channelID");
});
it("Should not be able to VIP downvote", (done) => {
postVote(tempVIPOne, UUID1, 0)
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID1);
assert.strictEqual(row.votes, 0);
done();
})
.catch(err => done(err));
it("Should not be able to VIP downvote", async () => {
const res = await postVote(tempVIPOne, UUID1, 0);
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID1);
assert.strictEqual(row.votes, 0);
});
it("Should not be able to VIP change category", (done) => {
postVoteCategory(tempVIPOne, UUID1, "filler")
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID1);
assert.strictEqual(row.category, "sponsor");
done();
})
.catch(err => done(err));
it("Should not be able to VIP change category", async () => {
const res = await postVoteCategory(tempVIPOne, UUID1, "filler");
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID1);
assert.strictEqual(row.category, "sponsor");
});
// error code testing
it("Should be able to add tempVIP after removal", (done) => {
addTempVIP("true", permVIP1, publicTempVIPOne)
.then(async res => {
assert.strictEqual(res.status, 200);
const vip = await checkUserVIP(publicTempVIPOne);
assert.strictEqual(vip, "ChannelID");
done();
})
.catch(err => done(err));
it("Should be able to add tempVIP after removal", async () => {
const res = await addTempVIP("true", permVIP1, publicTempVIPOne);
assert.strictEqual(res.status, 200);
const vip = await checkUserVIP(publicTempVIPOne);
assert.strictEqual(vip, "ChannelID");
});
it("Should not be able to add VIP without existing VIP (403)", (done) => {
it("Should not be able to add VIP without existing VIP (403)", async () => {
const privateID = "non-vip-privateid";
addTempVIP("true", privateID, publicTempVIPOne)
.then(async res => {
assert.strictEqual(res.status, 403);
const vip = await checkUserVIP(getHash(privateID) as HashedUserID);
assert.ok(!vip, "Should be no listed channelID");
done();
})
.catch(err => done(err));
const res = await addTempVIP("true", privateID, publicTempVIPOne);
assert.strictEqual(res.status, 403);
const vip = await checkUserVIP(getHash(privateID) as HashedUserID);
assert.ok(!vip, "Should be no listed channelID");
});
it("Should not be able to add permanant VIP as temporary VIP (409)", (done) => {
addTempVIP("true", permVIP1, publicPermVIP2)
.then(async res => {
assert.strictEqual(res.status, 409);
const vip = await checkUserVIP(publicPermVIP2);
assert.ok(!vip, "Should be no listed channelID");
done();
})
.catch(err => done(err));
it("Should not be able to add permanant VIP as temporary VIP (409)", async () => {
const res = await addTempVIP("true", permVIP1, publicPermVIP2);
assert.strictEqual(res.status, 409);
const vip = await checkUserVIP(publicPermVIP2);
assert.ok(!vip, "Should be no listed channelID");
});
it("Temp VIP should not be able to add another Temp VIP (403)", (done) => {
it("Temp VIP should not be able to add another Temp VIP (403)", async () => {
const privateID = "non-vip-privateid";
const publicID = getHash(privateID) as HashedUserID;
addTempVIP("true", tempVIPOne, publicID)
.then(async res => {
assert.strictEqual(res.status, 403);
const vip = await checkUserVIP(publicID);
assert.ok(!vip, "Should be no listed channelID");
done();
})
.catch(err => done(err));
const res = await addTempVIP("true", tempVIPOne, publicID);
assert.strictEqual(res.status, 403);
const vip = await checkUserVIP(publicID);
assert.ok(!vip, "Should be no listed channelID");
});
// error 40X testing
it("Should return 404 with invalid videoID", (done) => {
it("Should return 404 with invalid videoID", async () => {
const privateID = "non-vip-privateid";
const publicID = getHash(privateID) as HashedUserID;
addTempVIP("true", permVIP1, publicID, "knownWrongID")
.then(async res => {
assert.strictEqual(res.status, 404);
const vip = await checkUserVIP(publicID);
assert.ok(!vip, "Should be no listed channelID");
done();
})
.catch(err => done(err));
const res = await addTempVIP("true", permVIP1, publicID, "knownWrongID");
assert.strictEqual(res.status, 404);
const vip = await checkUserVIP(publicID);
assert.ok(!vip, "Should be no listed channelID");
});
it("Should return 400 with invalid userID", (done) => {
addTempVIP("true", permVIP1, "" as HashedUserID, "knownWrongID")
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
it("Should return 400 with invalid userID", async () => {
const res = await addTempVIP("true", permVIP1, "" as HashedUserID, "knownWrongID");
assert.strictEqual(res.status, 400);
});
it("Should return 400 with invalid adminUserID", (done) => {
addTempVIP("true", "", publicTempVIPOne)
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
it("Should return 400 with invalid adminUserID", async () => {
const res = await addTempVIP("true", "", publicTempVIPOne);
assert.strictEqual(res.status, 400);
});
it("Should return 400 with invalid channelID", (done) => {
addTempVIP("true", permVIP1, publicTempVIPOne, "")
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
it("Should return 400 with invalid channelID", async () => {
const res = await addTempVIP("true", permVIP1, publicTempVIPOne, "");
assert.strictEqual(res.status, 400);
});
});
});

View File

@@ -16,32 +16,24 @@ describe("tokenUtils test", function() {
mock.onGet(/identity/).reply(200, patreon.activeIdentity);
});
it("Should be able to create patreon token", function (done) {
if (!config?.patreon) this.skip();
tokenUtils.createAndSaveToken(tokenUtils.TokenType.patreon, "test_code").then((licenseKey) => {
assert.ok(validateToken(licenseKey[0]));
done();
});
it("Should be able to create patreon token", async function () {
if (!config?.patreon) return this.skip();
const licenseKey = await tokenUtils.createAndSaveToken(tokenUtils.TokenType.patreon, "test_code");
assert.ok(validateToken(licenseKey[0]));
});
it("Should be able to create local token", (done) => {
tokenUtils.createAndSaveToken(tokenUtils.TokenType.local).then((licenseKey) => {
assert.ok(validateToken(licenseKey[0]));
done();
});
it("Should be able to create local token", async () => {
const licenseKey = await tokenUtils.createAndSaveToken(tokenUtils.TokenType.local);
assert.ok(validateToken(licenseKey[0]));
});
it("Should be able to get patreon identity", function (done) {
if (!config?.patreon) this.skip();
tokenUtils.getPatreonIdentity("fake_access_token").then((result) => {
assert.deepEqual(result, patreon.activeIdentity);
done();
});
it("Should be able to get patreon identity", async function () {
if (!config?.patreon) return this.skip();
const result = await tokenUtils.getPatreonIdentity("fake_access_token");
assert.deepEqual(result, patreon.activeIdentity);
});
it("Should be able to refresh token", function (done) {
if (!config?.patreon) this.skip();
tokenUtils.refreshToken(tokenUtils.TokenType.patreon, "fake-licence-Key", "fake_refresh_token").then((result) => {
assert.strictEqual(result, true);
done();
});
it("Should be able to refresh token", async function () {
if (!config?.patreon) return this.skip();
const result = await tokenUtils.refreshToken(tokenUtils.TokenType.patreon, "fake-licence-Key", "fake_refresh_token");
assert.strictEqual(result, true);
});
after(function () {
@@ -56,20 +48,16 @@ describe("tokenUtils failing tests", function() {
mock.onGet(/identity/).reply(204, patreon.activeIdentity);
});
it("Should fail if patreon is not correctly stubbed", function (done) {
tokenUtils.createAndSaveToken(tokenUtils.TokenType.patreon, "test_code").then((licenseKey) => {
assert.strictEqual(licenseKey, null);
done();
});
it("Should fail if patreon is not correctly stubbed", async () => {
const licenseKey = await tokenUtils.createAndSaveToken(tokenUtils.TokenType.patreon, "test_code");
assert.strictEqual(licenseKey, null);
});
it("Should fail if token type is invalid", (done) => {
tokenUtils.createAndSaveToken("invalidTokenType" as tokenUtils.TokenType).then((licenseKey) => {
assert.strictEqual(licenseKey, null);
done();
});
it("Should fail if token type is invalid", async () => {
const licenseKey = await tokenUtils.createAndSaveToken("invalidTokenType" as tokenUtils.TokenType);
assert.strictEqual(licenseKey, null);
});
after(function () {
mock.restore();
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -59,12 +59,13 @@ async function init() {
mocha.run((failures) => {
mockServer.close();
server.close();
redis.quit();
process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures
process.exit();
redis.quit().finally(() => {
process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures
process.exit();
});
});
});
});
}
init();
void init();

51
test/utils/randomUsers.ts Normal file
View File

@@ -0,0 +1,51 @@
import { HashedUserID, UserID } from "../../src/types/user.model";
import { getHash } from "../../src/utils/getHash";
import { genRandom } from "./getRandom";
export interface TestUser {
private: UserID,
public: HashedUserID,
}
interface InternalTestUsers {
map: Map<any, TestUser>,
suiteName: string,
}
const userMapHandler = {
get(target: InternalTestUsers, property: string): TestUser {
const suiteName = Reflect.get(target, "suiteName");
const map = Reflect.get(target, "map");
let user = map.get(property);
if (user !== undefined) {
return user;
}
const priv = `${suiteName}-${property}-${genRandom}` as UserID;
user = {
private: priv,
public: getHash(priv),
};
map.set(property, user);
return user;
},
set: () => false, // nope
deleteProperty: () => false, // nope
has: () => true, // yep
defineProperty: () => false, // nope
preventExtensions: () => false, // nope
setPrototypeOf: () => false, // nope
};
/**
* Creates an object that generates test private/public userID pairs on demand
*
* @param suiteName the suite name, used as a prefix for the private userID
* @returns a proxy that generates & caches private/public userID pairs for each property access
*/
export function usersForSuite(suiteName: string): Record<any, TestUser> {
return new Proxy({
map: new Map<any, TestUser>(),
suiteName,
}, userMapHandler) as unknown as Record<any, TestUser>;
}

4
tsconfig.eslint.json Normal file
View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"include": ["**/*"],
}