Files
anonymous_github/test/dependency-redis-integration.test.js

82 lines
3.9 KiB
JavaScript

/* global fetch */
const { expect } = require("chai");
const express = require("express");
const session = require("express-session");
const { createClient } = require("redis");
const { RedisStore: SessionStore } = require("connect-redis");
const { RedisStore: RateLimitStore } = require("rate-limit-redis");
const { rateLimit } = require("express-rate-limit");
const { execFile } = require("node:child_process");
const { promisify } = require("node:util");
const path = require("node:path");
const port = Number(process.env.PERFORMANCE_REDIS_PORT);
(port ? describe : describe.skip)("upgraded Redis stores and session cleanup", function () {
this.timeout(15000);
let redis, server, base;
const prefix = `dependency-test:${process.pid}:`;
before(async function () {
redis = createClient({ socket: { host: "127.0.0.1", port, reconnectStrategy: false } });
redis.on("error", () => {});
await redis.connect();
const app = express();
app.use(session({ secret: "dependency-test", resave: false, saveUninitialized: false,
store: new SessionStore({ client: redis, prefix: `${prefix}session:` }) }));
app.get("/session", (req, res) => {
req.session.visits = (req.session.visits || 0) + 1;
res.json({ visits: req.session.visits });
});
const limiter = () => rateLimit({ windowMs: 60000, limit: 1, keyGenerator: () => "client",
store: new RateLimitStore({ prefix: `${prefix}limit:`, sendCommand: (...args) => redis.sendCommand(args) }) });
app.get("/first", limiter(), (_req, res) => res.send("ok"));
app.get("/second", limiter(), (_req, res) => res.send("ok"));
await new Promise(resolve => { server = app.listen(0, "127.0.0.1", resolve); });
base = `http://127.0.0.1:${server.address().port}`;
});
after(async function () {
if (server) {
server.closeAllConnections();
await new Promise(resolve => server.close(resolve));
}
if (redis?.isOpen) {
for await (const keys of redis.scanIterator({ MATCH: `${prefix}*` })) {
if (keys.length) await redis.del(keys);
}
await redis.del([`anoGH_session:${prefix}legacy`, `anoGH_session:${prefix}current`]);
await redis.close();
}
});
it("persists sessions across HTTP requests", async function () {
const first = await fetch(`${base}/session`);
expect(await first.json()).to.deep.equal({ visits: 1 });
const cookie = first.headers.get("set-cookie").split(";")[0];
const second = await fetch(`${base}/session`, { headers: { Cookie: cookie } });
expect(await second.json()).to.deep.equal({ visits: 2 });
});
it("shares a request quota between separate Redis store instances", async function () {
expect((await fetch(`${base}/first`)).status).to.equal(200);
expect((await fetch(`${base}/second`)).status).to.equal(429);
});
it("scans session batches, preserves current sessions and applies legacy cleanup", async function () {
const legacy = `anoGH_session:${prefix}legacy`, current = `anoGH_session:${prefix}current`;
await redis.set(legacy, JSON.stringify({ passport: { user: { id: "legacy" } } }));
await redis.set(current, JSON.stringify({ passport: { user: "current" } }));
const run = async args => {
const result = await promisify(execFile)(process.execPath,
["-r", "ts-node/register", "src/scripts/purge-legacy-sessions.ts", ...args], {
cwd: path.resolve(__dirname, ".."), timeout: 10000,
env: { ...process.env, NODE_ENV: "test", REDIS_HOSTNAME: "127.0.0.1", REDIS_PORT: String(port) },
});
return JSON.parse(result.stdout.trim().split("\n").at(-1));
};
expect(await run([])).to.deep.equal({ found: 1, removed: 0 });
expect(await redis.exists(legacy)).to.equal(1);
expect(await run(["--apply"])).to.deep.equal({ found: 1, removed: 1 });
expect(await redis.exists(legacy)).to.equal(0);
expect(await redis.get(current)).to.equal(JSON.stringify({ passport: { user: "current" } }));
});
});