diff --git a/.github/workflows/postgres-ci.yml b/.github/workflows/postgres-redis-ci.yml similarity index 87% rename from .github/workflows/postgres-ci.yml rename to .github/workflows/postgres-redis-ci.yml index e0f1519..28a19f2 100644 --- a/.github/workflows/postgres-ci.yml +++ b/.github/workflows/postgres-redis-ci.yml @@ -1,10 +1,10 @@ -name: PostgreSQL CI +name: PostgreSQL + Redis CI on: [push, pull_request] jobs: build: - name: Run Tests with PostgreSQL + name: Run Tests with PostgreSQL and Redis runs-on: ubuntu-latest steps: diff --git a/ci.json b/ci.json index 2f948b7..68262cb 100644 --- a/ci.json +++ b/ci.json @@ -16,6 +16,10 @@ "host": "localhost", "port": 5432 }, + "redis": { + "host": "localhost", + "port": 6379 + }, "createDatabaseIfNotExist": true, "schemaFolder": "./databases", "dbSchema": "./databases/_sponsorTimes.db.sql", diff --git a/docker/docker-compose-ci.yml b/docker/docker-compose-ci.yml index acfd112..7e64c76 100644 --- a/docker/docker-compose-ci.yml +++ b/docker/docker-compose-ci.yml @@ -6,4 +6,8 @@ services: - POSTGRES_USER=${PG_USER} - POSTGRES_PASSWORD=${PG_PASS} ports: - - 5432:5432 \ No newline at end of file + - 5432:5432 + redis: + image: redis:alpine + ports: + - 6379:6379 \ No newline at end of file diff --git a/package.json b/package.json index 4355380..30d912f 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "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", + "redis:docker": "docker run --rm -p 6379:6379 redis:alpine", "start": "ts-node src/index.ts", "tsc": "tsc -p tsconfig.json", "lint": "eslint src test", diff --git a/src/utils/redis.ts b/src/utils/redis.ts index ef2a640..8bdea23 100644 --- a/src/utils/redis.ts +++ b/src/utils/redis.ts @@ -8,6 +8,7 @@ interface RedisSB { set(key: string, value: string, callback?: Callback): void; setAsync?(key: string, value: string): Promise<{err: Error | null, reply: string | null}>; delAsync?(...keys: [string]): Promise; + close?(flush?: boolean): void; } let exportObject: RedisSB = { @@ -29,6 +30,7 @@ if (config.redis) { exportObject.getAsync = (key) => new Promise((resolve) => client.get(key, (err, reply) => resolve({ err, reply }))); exportObject.setAsync = (key, value) => new Promise((resolve) => client.set(key, value, (err, reply) => resolve({ err, reply }))); exportObject.delAsync = (...keys) => new Promise((resolve) => client.del(keys, (err) => resolve(err))); + exportObject.close = (flush) => client.end(flush); client.on("error", function(error) { Logger.error(error); diff --git a/test/cases/getSkipSegmentsByHash.ts b/test/cases/getSkipSegmentsByHash.ts index d0236e1..6763651 100644 --- a/test/cases/getSkipSegmentsByHash.ts +++ b/test/cases/getSkipSegmentsByHash.ts @@ -1,5 +1,5 @@ import { db } from "../../src/databases/databases"; -import { partialDeepEquals } from "../utils/partialDeepEquals"; +import { partialDeepEquals, arrayPartialDeepEquals } from "../utils/partialDeepEquals"; import { getHash } from "../../src/utils/getHash"; import { ImportMock, } from "ts-mock-imports"; import * as YouTubeAPIModule from "../../src/utils/youtubeApi"; @@ -434,7 +434,7 @@ describe("getSkipSegmentsByHash", () => { }] }]; - assert.ok(partialDeepEquals(data, expected, false) || partialDeepEquals(data, expected2, false)); + assert.ok(arrayPartialDeepEquals(data, expected) || arrayPartialDeepEquals(data, expected2)); assert.strictEqual(data[0].segments.length, 3); done(); }) diff --git a/test/cases/ratings/getRating.ts b/test/cases/ratings/getRating.ts index 3ba3909..1ab3737 100644 --- a/test/cases/ratings/getRating.ts +++ b/test/cases/ratings/getRating.ts @@ -3,7 +3,7 @@ import { getHash } from "../../../src/utils/getHash"; import assert from "assert"; import { client } from "../../utils/httpClient"; import { AxiosResponse } from "axios"; -import { partialDeepEquals } from "../../utils/partialDeepEquals"; +import { partialDeepEquals, arrayPartialDeepEquals } from "../../utils/partialDeepEquals"; const endpoint = "/api/ratings/rate"; const getRating = (hash: string, params?: unknown): Promise => client.get(`${endpoint}/${hash}`, { params }); @@ -58,6 +58,9 @@ describe("getRating", () => { .catch(err => done(err)); }); + /* + This test will fail if tests are already ran with redis. + */ it("Should be able to bulk fetch", (done) => { getBulkRating([videoOnePartialHash, videoTwoPartialHash]) .then(res => { @@ -80,7 +83,7 @@ describe("getRating", () => { count: 10, hash: videoOneIDHash, }]; - assert.ok(partialDeepEquals(res.data, expected)); + assert.ok(arrayPartialDeepEquals(res.data, expected)); done(); }) .catch(err => done(err)); diff --git a/test/cases/redisTest.ts b/test/cases/redisTest.ts new file mode 100644 index 0000000..cc99edc --- /dev/null +++ b/test/cases/redisTest.ts @@ -0,0 +1,32 @@ +import { config } from "../../src/config"; +import redis from "../../src/utils/redis"; +import crypto from "crypto"; +import assert from "assert"; + +const genRandom = (bytes=8) => crypto.pseudoRandomBytes(bytes).toString("hex"); + +const randKey1 = genRandom(); +const randValue1 = genRandom(); +const randKey2 = genRandom(16); + +describe("redis test", function() { + before(async function() { + if (!config.redis) this.skip(); + await redis.setAsync(randKey1, randValue1); + }); + it("Should get stored value", (done) => { + redis.getAsync(randKey1) + .then(res => { + if (res.err) assert.fail(res.err); + assert.strictEqual(res.reply, randValue1); + done(); + }); + }); + it("Should not be able to get not stored value", (done) => { + redis.getAsync(randKey2) + .then(res => { + if (res.reply || res.err ) assert.fail("Value should not be found") + done(); + }); + }) +}); \ No newline at end of file diff --git a/test/test.ts b/test/test.ts index 3484add..b53879b 100644 --- a/test/test.ts +++ b/test/test.ts @@ -9,6 +9,7 @@ import { initDb } from "../src/databases/databases"; import { ImportMock } from "ts-mock-imports"; import * as rateLimitMiddlewareModule from "../src/middleware/requestRateLimit"; import rateLimit from "express-rate-limit"; +import redis from "../src/utils/redis"; async function init() { ImportMock.mockFunction(rateLimitMiddlewareModule, "rateLimitMiddleware", rateLimit({ @@ -56,6 +57,7 @@ async function init() { mocha.run((failures) => { mockServer.close(); server.close(); + redis.close(true); process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures }); }); diff --git a/test/utils/partialDeepEquals.ts b/test/utils/partialDeepEquals.ts index df651d6..b2832f6 100644 --- a/test/utils/partialDeepEquals.ts +++ b/test/utils/partialDeepEquals.ts @@ -22,6 +22,12 @@ export const partialDeepEquals = (actual: Record, expected: Record< return true; }; +export const arrayPartialDeepEquals = (actual: Array, expected: Array): boolean => { + for (const value of expected) + if (!actual.some(a => partialDeepEquals(a, value, false))) return false; + return true; +}; + export const arrayDeepEquals = (actual: Record, expected: Record, print = true): boolean => { if (actual.length !== expected.length) return false; let flag = true;