From cc8a3d8b5e3916c54d8e44f0d6dd334f211fa005 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 17 Aug 2025 18:42:20 -0400 Subject: [PATCH] chore: Still seeing redis connection failures in CI (#9957) --- .jestconfig.json | 1 - package.json | 2 ++ server/queues/queue.ts | 4 +-- server/storage/__mocks__/redis.ts | 52 ---------------------------- server/test/globalSetup.js | 14 -------- server/test/setup.ts | 21 ++++++++---- yarn.lock | 56 ++++++++++++++++++++++++++----- 7 files changed, 65 insertions(+), 85 deletions(-) delete mode 100644 server/storage/__mocks__/redis.ts delete mode 100644 server/test/globalSetup.js diff --git a/.jestconfig.json b/.jestconfig.json index c45e43e75e..7276b01548 100644 --- a/.jestconfig.json +++ b/.jestconfig.json @@ -12,7 +12,6 @@ }, "setupFiles": ["/__mocks__/console.js"], "setupFilesAfterEnv": ["/server/test/setup.ts"], - "globalSetup": "/server/test/globalSetup.js", "globalTeardown": "/server/test/globalTeardown.js", "testEnvironment": "node" }, diff --git a/package.json b/package.json index 11bdca0f0d..834f886071 100644 --- a/package.json +++ b/package.json @@ -295,6 +295,7 @@ "@types/glob": "^8.0.1", "@types/google.analytics": "^0.0.46", "@types/invariant": "^2.2.37", + "@types/ioredis-mock": "^8.2.6", "@types/jest": "^29.5.14", "@types/jsonwebtoken": "^8.5.9", "@types/katex": "^0.16.7", @@ -353,6 +354,7 @@ "discord-api-types": "^0.37.119", "husky": "^8.0.3", "i18next-parser": "^8.13.0", + "ioredis-mock": "^8.9.0", "jest-cli": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-fetch-mock": "^3.0.3", diff --git a/server/queues/queue.ts b/server/queues/queue.ts index 03ea4f673e..164d1d0ff6 100644 --- a/server/queues/queue.ts +++ b/server/queues/queue.ts @@ -4,6 +4,7 @@ import snakeCase from "lodash/snakeCase"; import { Second } from "@shared/utils/time"; import env from "@server/env"; import Metrics from "@server/logging/Metrics"; +import Redis from "@server/storage/redis"; import ShutdownHelper, { ShutdownOrder } from "@server/utils/ShutdownHelper"; export function createQueue( @@ -16,9 +17,6 @@ export function createQueue( // https://github.com/OptimalBits/bull/blob/b6d530f72a774be0fd4936ddb4ad9df3b183f4b6/PATTERNS.md#reusing-redis-connections const queue = new Queue(name, { createClient(type) { - // Load dynamically so that this isn't pulled in at startup during tests - const Redis = require("../storage/redis").default; - switch (type) { case "client": return Redis.defaultClient; diff --git a/server/storage/__mocks__/redis.ts b/server/storage/__mocks__/redis.ts deleted file mode 100644 index 3727575fd7..0000000000 --- a/server/storage/__mocks__/redis.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { EventEmitter } from "events"; - -// Create a mock Redis client with all needed methods mocked -class RedisMock extends EventEmitter { - constructor() { - super(); - } - - get = jest.fn().mockResolvedValue(null); - set = jest.fn().mockResolvedValue("OK"); - del = jest.fn().mockResolvedValue(1); - hget = jest.fn().mockResolvedValue(null); - hset = jest.fn().mockResolvedValue("OK"); - hdel = jest.fn().mockResolvedValue(1); - sadd = jest.fn().mockResolvedValue(1); - smembers = jest.fn().mockResolvedValue([]); - keys = jest.fn().mockResolvedValue([]); - ping = jest.fn().mockResolvedValue("PONG"); - disconnect = jest.fn(); - setMaxListeners = jest.fn(); -} - -// Mock the RedisAdapter class -class RedisAdapter extends RedisMock { - constructor(_url: string | undefined, _options = {}) { - super(); - } - - private static client: RedisAdapter; - private static subscriber: RedisAdapter; - - public static get defaultClient(): RedisAdapter { - return ( - this.client || - (this.client = new this(undefined, { - connectionNameSuffix: "client", - })) - ); - } - - public static get defaultSubscriber(): RedisAdapter { - return ( - this.subscriber || - (this.subscriber = new this(undefined, { - maxRetriesPerRequest: null, - connectionNameSuffix: "subscriber", - })) - ); - } -} - -export default RedisAdapter; diff --git a/server/test/globalSetup.js b/server/test/globalSetup.js deleted file mode 100644 index 268e4110bf..0000000000 --- a/server/test/globalSetup.js +++ /dev/null @@ -1,14 +0,0 @@ -import { sequelize } from "@server/storage/database"; - -module.exports = async function () { - const sql = sequelize.getQueryInterface(); - const tables = Object.keys(sequelize.models).map((model) => { - const n = sequelize.models[model].getTableName(); - return sql.queryGenerator.quoteTable( - typeof n === "string" ? n : n.tableName - ); - }); - const flushQuery = `TRUNCATE ${tables.join(", ")} CASCADE`; - - await sequelize.query(flushQuery); -}; diff --git a/server/test/setup.ts b/server/test/setup.ts index 3a289d7ab0..2d56782398 100644 --- a/server/test/setup.ts +++ b/server/test/setup.ts @@ -1,18 +1,18 @@ import "reflect-metadata"; import sharedEnv from "@shared/env"; import env from "@server/env"; - -require("jest-fetch-mock").enableMocks(); -fetchMock.dontMock(); - -require("@server/storage/database"); +import Redis from "ioredis-mock"; // Enable mocks for Redis-related modules -jest.mock("@server/storage/redis"); +jest.mock("ioredis", () => require("ioredis-mock")); jest.mock("@server/utils/MutexLock"); jest.mock("@server/utils/CacheHelper"); -// We never want to make real S3 requests in test environment +// Enable fetch mocks for testing +require("jest-fetch-mock").enableMocks(); +fetchMock.dontMock(); + +// Mock AWS SDK S3 client and related commands jest.mock("@aws-sdk/client-s3", () => ({ S3Client: jest.fn(() => ({ send: jest.fn(), @@ -36,6 +36,13 @@ jest.mock("@aws-sdk/s3-request-presigner", () => ({ getSignedUrl: jest.fn(), })); +// Initialize the database models +require("@server/storage/database"); + beforeEach(() => { env.URL = sharedEnv.URL = "https://app.outline.dev"; }); + +afterEach((done) => { + new Redis().flushall().then(() => done()); +}); diff --git a/yarn.lock b/yarn.lock index 68f95b11ab..3c48161cc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2286,10 +2286,15 @@ resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8" integrity "sha1-6QyfcXaLNzbnbX3WeD/Gwq+oi8g= sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" -"@ioredis/commands@^1.0.2", "@ioredis/commands@^1.1.1": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" - integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== +"@ioredis/as-callback@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@ioredis/as-callback/-/as-callback-3.0.0.tgz#b96c9b05e6701e85ec6a5e62fa254071b0aec97f" + integrity sha512-Kqv1rZ3WbgOrS+hgzJ5xG5WQuhvzzSTRYvNeyPMLOAM78MHSnuKI20JeJGbpuAt//LCuP0vsexZcorqW7kWhJg== + +"@ioredis/commands@^1.0.2", "@ioredis/commands@^1.1.1", "@ioredis/commands@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.3.0.tgz#4dc3ae9bfa7146b63baf27672a61db0ea86e35e5" + integrity sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ== "@isaacs/cliui@^8.0.2": version "8.0.2" @@ -4943,6 +4948,11 @@ resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.37.tgz#1709741e534364d653c87dff22fc76fa94aa7bc0" integrity sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A== +"@types/ioredis-mock@^8.2.6": + version "8.2.6" + resolved "https://registry.yarnpkg.com/@types/ioredis-mock/-/ioredis-mock-8.2.6.tgz#efc80b2fe702d3c3b9ee70f3fe05a36c18b2a528" + integrity sha512-5heqtZMvQ4nXARY0o8rc8cjkJjct2ScM12yCJ/h731S9He93a2cv+kAhwPCNwTKDfNH9gjRfLG4VpAEYJU0/gQ== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" @@ -8247,6 +8257,20 @@ fecha@^4.2.0: resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.1.tgz#0a83ad8f86ef62a091e22bb5a039cd03d23eecce" integrity "sha1-CoOtj4bvYqCR4iu1oDnNA9I+7M4= sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==" +fengari-interop@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/fengari-interop/-/fengari-interop-0.1.3.tgz#3ad37a90e7430b69b365441e9fc0ba168942a146" + integrity sha512-EtZ+oTu3kEwVJnoymFPBVLIbQcCoy9uWCVnMA6h3M/RqHkUBsLYp29+RRHf9rKr6GwjubWREU1O7RretFIXjHw== + +fengari@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/fengari/-/fengari-0.1.4.tgz#72416693cd9e43bd7d809d7829ddc0578b78b0bb" + integrity sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g== + dependencies: + readline-sync "^1.4.9" + sprintf-js "^1.1.1" + tmp "^0.0.33" + fetch-retry@^5.0.6: version "5.0.6" resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-5.0.6.tgz#17d0bc90423405b7a88b74355bf364acd2a7fa56" @@ -9170,6 +9194,17 @@ invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" +ioredis-mock@^8.9.0: + version "8.9.0" + resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-8.9.0.tgz#5d694c4b81d3835e4291e0b527f947e260981779" + integrity sha512-yIglcCkI1lvhwJVoMsR51fotZVsPsSk07ecTCgRTRlicG0Vq3lke6aAaHklyjmRNRsdYAgswqC2A0bPtQK4LSw== + dependencies: + "@ioredis/as-callback" "^3.0.0" + "@ioredis/commands" "^1.2.0" + fengari "^0.1.4" + fengari-interop "^0.1.3" + semver "^7.5.4" + ioredis@^4.28.2: version "4.30.0" resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.30.0.tgz#023277fcbeddd2dba3c101ef45f26c3f1de98a92" @@ -12738,6 +12773,11 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +readline-sync@^1.4.9: + version "1.4.10" + resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" + integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== + reakit-system@^0.15.2: version "0.15.2" resolved "https://registry.yarnpkg.com/reakit-system/-/reakit-system-0.15.2.tgz#a485fab84b3942acbed6212c3b56a6ef8611c457" @@ -13659,10 +13699,10 @@ split2@^3.1.0, split2@^3.1.1: dependencies: readable-stream "^3.0.0" -sprintf-js@^1.0.3: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" - integrity "sha1-2hdlJiv4wPVxdJ8q1sJjACB65nM= sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" +sprintf-js@^1.0.3, sprintf-js@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== sprintf-js@~1.0.2: version "1.0.3"