diff --git a/ci.json b/ci.json index 7cdfe67..30e2f41 100644 --- a/ci.json +++ b/ci.json @@ -17,10 +17,12 @@ "port": 5432 }, "redis": { + "enabled": true, "socket": { "host": "localhost", "port": 6379 - } + }, + "expiryTime": 86400 }, "createDatabaseIfNotExist": true, "schemaFolder": "./databases", diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 7b6e1b5..acdd62b 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: database: container_name: database - image: postgres:13 + image: postgres:14 env_file: - database.env volumes: @@ -12,7 +12,7 @@ services: restart: always redis: container_name: redis - image: redis:6.0 + image: redis:7.0 command: /usr/local/etc/redis/redis.conf volumes: - ./redis/redis.conf:/usr/local/etc/redis/redis.conf diff --git a/package.json b/package.json index 0f92f7c..ce1b24c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/index.ts", "scripts": { "test": "npm run tsc && ts-node test/test.ts", - "test:coverate": "nyc npm run test", + "test:coverage": "nyc npm run test", "dev": "nodemon", "dev:bash": "nodemon -x 'npm test ; npm start'", "postgres:docker": "docker run --rm -p 5432:5432 -e POSTGRES_USER=ci_db_user -e POSTGRES_PASSWORD=ci_db_pass postgres:alpine", diff --git a/src/routes/dumpDatabase.ts b/src/routes/dumpDatabase.ts index 73557ad..ca579d6 100644 --- a/src/routes/dumpDatabase.ts +++ b/src/routes/dumpDatabase.ts @@ -5,7 +5,7 @@ import { config } from "../config"; import util from "util"; import fs from "fs"; import path from "path"; -import { ChildProcess, exec, ExecOptions, spawn } from "child_process"; +import { exec, ExecOptions } from "child_process"; const unlink = util.promisify(fs.unlink); const ONE_MINUTE = 1000 * 60; @@ -44,7 +44,7 @@ const credentials: ExecOptions = { PGPASSWORD: String(config.postgres.password), PGDATABASE: "sponsorTimes", } -} +}; interface TableDumpList { fileName: string; @@ -232,7 +232,7 @@ async function queueDump(): Promise { resolve(error ? stderr : stdout); }); - }) + }); dumpFiles.push({ fileName, diff --git a/src/utils/getHashCache.ts b/src/utils/getHashCache.ts index b3d0d7b..7ff0ef0 100644 --- a/src/utils/getHashCache.ts +++ b/src/utils/getHashCache.ts @@ -26,11 +26,13 @@ async function getFromRedis(key: HashedValue): Promise Logger.error(err)); + redis.set(redisKey, data).catch((err) => Logger.error(err)); return data as T & HashedValue; } \ No newline at end of file diff --git a/src/utils/redis.ts b/src/utils/redis.ts index 06a89d7..6ebdeb4 100644 --- a/src/utils/redis.ts +++ b/src/utils/redis.ts @@ -33,7 +33,7 @@ if (config.redis?.enabled) { const get = client.get.bind(client); exportClient.get = (key) => new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(), config.redis.getTimeout); + const timeout = config.redis.getTimeout ? setTimeout(() => reject(), config.redis.getTimeout) : null; get(key).then((reply) => { clearTimeout(timeout); resolve(reply); @@ -48,7 +48,10 @@ if (config.redis?.enabled) { .catch((err) => reject(err)) ); client.on("error", function(error) { - Logger.error(error); + Logger.error(`Redis Error: ${error}`); + }); + client.on("reconnect", () => { + Logger.info("Redis: trying to reconnect"); }); } diff --git a/test/cases/getHashCache.ts b/test/cases/getHashCache.ts new file mode 100644 index 0000000..40434e6 --- /dev/null +++ b/test/cases/getHashCache.ts @@ -0,0 +1,31 @@ +import { config } from "../../src/config"; +import { getHashCache } from "../../src/utils/getHashCache"; +import { shaHashKey } from "../../src/utils/redisKeys"; +import { getHash } from "../../src/utils/getHash"; +import redis from "../../src/utils/redis"; +import crypto from "crypto"; +import assert from "assert"; +import { setTimeout } from "timers/promises"; + +const genRandom = (bytes=8) => crypto.pseudoRandomBytes(bytes).toString("hex"); + +const rand1Hash = genRandom(24); +const rand1Hash_Key = getHash(rand1Hash, 1); +const rand1Hash_Result = getHash(rand1Hash); + +describe("getHashCache test", function() { + before(function() { + if (!config.redis?.enabled) this.skip(); + }); + it("Should set hashKey and be able to retreive", (done) => { + const redisKey = shaHashKey(rand1Hash_Key); + getHashCache(rand1Hash) + .then(() => setTimeout(50)) // add timeout for redis to complete async + .then(() => redis.get(redisKey)) + .then(result => { + assert.strictEqual(result, rand1Hash_Result); + done(); + }) + .catch(err => done(err === undefined ? "no set value" : err)); + }).timeout(5000); +}); \ No newline at end of file diff --git a/test/cases/getLockReason.ts b/test/cases/getLockReason.ts index 1bb0dfd..9bbb35b 100644 --- a/test/cases/getLockReason.ts +++ b/test/cases/getLockReason.ts @@ -31,7 +31,7 @@ describe("getLockReason", () => { }); after(async () => { - const deleteUserNameQuery = 'DELETE FROM "userNames" WHERE "userID" = ? AND "userName" = ? LIMIT 1'; + const deleteUserNameQuery = 'DELETE FROM "userNames" WHERE "userID" = ? AND "userName" = ?'; await db.prepare("run", deleteUserNameQuery, [vipUserID1, vipUserName1]); await db.prepare("run", deleteUserNameQuery, [vipUserID2, vipUserName2]); }); diff --git a/test/cases/postSkipSegments.ts b/test/cases/postSkipSegments.ts index 422ea88..1369282 100644 --- a/test/cases/postSkipSegments.ts +++ b/test/cases/postSkipSegments.ts @@ -93,7 +93,6 @@ describe("postSkipSegments", () => { db.prepare("run", insertWarningQuery, [warnUser01Hash, warnVip01Hash, 1, reason01, (now - 3601000)]); // User 2 db.prepare("run", insertWarningQuery, [warnUser02Hash, warnVip01Hash, 1, reason02, now]); - db.prepare("run", insertWarningQuery, [warnUser02Hash, warnVip01Hash, 1, reason02, now]); db.prepare("run", insertWarningQuery, [warnUser02Hash, warnVip01Hash, 1, reason02, (now - (warningExpireTime + 1000))]); db.prepare("run", insertWarningQuery, [warnUser02Hash, warnVip01Hash, 1, reason02, (now - (warningExpireTime + 2000))]); // User 3 diff --git a/test/cases/redisTest.ts b/test/cases/redisTest.ts index a6d21bb..d2f7be4 100644 --- a/test/cases/redisTest.ts +++ b/test/cases/redisTest.ts @@ -8,6 +8,8 @@ const genRandom = (bytes=8) => crypto.pseudoRandomBytes(bytes).toString("hex"); const randKey1 = genRandom(); const randValue1 = genRandom(); const randKey2 = genRandom(16); +const randKey3 = genRandom(); +const randValue3 = genRandom(); describe("redis test", function() { before(async function() { @@ -19,13 +21,41 @@ describe("redis test", function() { .then(res => { assert.strictEqual(res, randValue1); done(); - }).catch(err => assert.fail(err)); + }).catch(err => done(err)); }); it("Should not be able to get not stored value", (done) => { redis.get(randKey2) .then(res => { - if (res) assert.fail("Value should not be found"); + if (res) done("Value should not be found"); done(); - }).catch(err => assert.fail(err)); + }).catch(err => done(err)); + }); + it("Should be able to delete stored value", (done) => { + redis.del(randKey1) + .then(() => { + redis.get(randKey1) + .then(res => { + assert.strictEqual(res, null); + done(); + }).catch(err => done(err)); + }).catch(err => done(err)); + }); + it("Should be able to set expiring value", (done) => { + redis.setEx(randKey3, 8400, randValue3) + .then(() => { + redis.get(randKey3) + .then(res => { + assert.strictEqual(res, randValue3); + done(); + }).catch(err => done(err)); + }).catch(err => done(err)); + }); + it("Should continue when undefined value is fetched", (done) => { + const undefkey = `undefined.${genRandom()}`; + redis.get(undefkey) + .then(result => { + assert.ok(!result); // result should be falsy + done(); + }); }); }); \ No newline at end of file diff --git a/test/cases/tempVip.ts b/test/cases/tempVip.ts index 6f23913..5448598 100644 --- a/test/cases/tempVip.ts +++ b/test/cases/tempVip.ts @@ -6,14 +6,13 @@ import { client } from "../utils/httpClient"; import { db, privateDB } from "../../src/databases/databases"; import redis from "../../src/utils/redis"; import assert from "assert"; -import { Logger } from "../../src/utils/logger"; // helpers const getSegment = (UUID: string) => db.prepare("get", `SELECT "votes", "locked", "category" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]); -const permVIP1 = "tempVipPermOne"; +const permVIP1 = "tempVip_permaVIPOne"; const publicPermVIP1 = getHash(permVIP1) as HashedUserID; -const permVIP2 = "tempVipPermOne"; +const permVIP2 = "tempVip_permaVIPTwo"; const publicPermVIP2 = getHash(permVIP2) as HashedUserID; const tempVIPOne = "tempVipTempOne"; @@ -51,15 +50,8 @@ const postVoteCategory = (userID: string, UUID: string, category: string) => cli category } }); -const checkUserVIP = async (publicID: HashedUserID) => { - try { - const reply = await redis.get(tempVIPKey(publicID)); - return reply; - } catch (e) { - Logger.error(e as string); - return false; - } -}; +const checkUserVIP = async (publicID: HashedUserID): Promise => + await redis.get(tempVIPKey(publicID)); describe("tempVIP test", function() { before(async function() { @@ -152,7 +144,7 @@ describe("tempVIP test", function() { .catch(err => done(err)); }); it("Should be able to remove tempVIP prematurely", (done) => { - addTempVIP("false", permVIP1, publicTempVIPOne, null) + addTempVIP("false", permVIP1, publicTempVIPOne) .then(async res => { assert.strictEqual(res.status, 200); const vip = await checkUserVIP(publicTempVIPOne); diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts index d189eff..44ad178 100644 --- a/test/cases/voteOnSponsorTime.ts +++ b/test/cases/voteOnSponsorTime.ts @@ -78,7 +78,6 @@ describe("voteOnSponsorTime", () => { await db.prepare("run", insertWarningQuery, [warnUser01Hash, (now - 2000), warnVip01Hash, 1]); await db.prepare("run", insertWarningQuery, [warnUser01Hash, (now - 3601000), warnVip01Hash, 1]); await db.prepare("run", insertWarningQuery, [warnUser02Hash, now, warnVip01Hash, 1]); - await db.prepare("run", insertWarningQuery, [warnUser02Hash, now, warnVip01Hash, 1]); await db.prepare("run", insertWarningQuery, [warnUser02Hash, (now - (warningExpireTime + 1000)), warnVip01Hash, 1]); await db.prepare("run", insertWarningQuery, [warnUser02Hash, (now - (warningExpireTime + 2000)), warnVip01Hash, 1]);