mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
## Summary Currently it's hard to debug integration tests because of all the extra logging/warnings/open handler issues (wasn't sure if I introduced an open handler or was existing). This fixes that. - Bump Jest from v28 to v30 in api and common-utils packages, syncing with app's existing setup - Replace legacy `preset: 'ts-jest'` with modern `createJsWithTsPreset()` spread pattern across all packages - Fix open handles that required `--forceExit` and `--detectOpenHandles` workarounds - Suppress noisy console and logger output during test runs via targeted mocks ## Changes ### Jest/ts-jest upgrade - Bump `jest` 28 → 30, `@types/jest` 28 → 29 in api and common-utils - Adopt `createJsWithTsPreset()` config pattern (matching app) - Add `isolatedModules: true` to common-utils tsconfig to fix ts-jest warning with Node16 module kind - Update snapshot files and inline snapshots for Jest 30 format changes - Replace removed `toThrowError()` with `toThrow()` - Fix team.test.ts inline snapshot that depended on dynamic Mongo ObjectIds ### Open handle fixes - Add `close()` method to `BaseClickhouseClient` in common-utils - Call `mongoose.disconnect()` in `closeDB()` (api fixtures) - Add `closeTestFixtureClickHouseClient()` and call it in `MockServer.stop()` - Fix common-utils integration tests to close both `hdxClient` and raw `client` in `afterAll` - Disable `usageStats()` interval in CI to prevent leaked timers - Remove `--forceExit` from common-utils CI/dev scripts and api dev script - Remove `--detectOpenHandles` from all dev scripts ### Log noise suppression - Use `jest.spyOn` for console methods instead of global console object override - Add per-file `console.warn`/`console.error` suppression in test files that exercise error paths - Mock pino logger module in api jest.setup.ts to suppress expected operational logs (validation errors, MCP tool errors, etc.) - Use pino logger instead of `console.error` in Express error handler (`middleware/error.ts`) - Add console suppression to app setupTests.tsx ## Testing - `make ci-lint` — passes - `make ci-unit` — 2177 tests pass, zero console noise - `make ci-int` — 642 tests pass (606 api + 36 common-utils), zero log noise
133 lines
4.8 KiB
TypeScript
133 lines
4.8 KiB
TypeScript
import compression from 'compression';
|
|
import MongoStore from 'connect-mongo';
|
|
import express from 'express';
|
|
import session from 'express-session';
|
|
import onHeaders from 'on-headers';
|
|
|
|
import * as config from './config';
|
|
import mcpRouter from './mcp/app';
|
|
import { isUserAuthenticated } from './middleware/auth';
|
|
import defaultCors from './middleware/cors';
|
|
import { appErrorHandler } from './middleware/error';
|
|
import routers from './routers/api';
|
|
import clickhouseProxyRouter from './routers/api/clickhouseProxy';
|
|
import connectionsRouter from './routers/api/connections';
|
|
import favoritesRouter from './routers/api/favorites';
|
|
import savedSearchRouter from './routers/api/savedSearch';
|
|
import sourcesRouter from './routers/api/sources';
|
|
import externalRoutersV2 from './routers/external-api/v2';
|
|
import usageStats from './tasks/usageStats';
|
|
import logger, { expressLogger } from './utils/logger';
|
|
import passport from './utils/passport';
|
|
|
|
const app: express.Application = express();
|
|
|
|
const sess: session.SessionOptions & { cookie: session.CookieOptions } = {
|
|
// Use a slot-specific cookie name in dev so multiple worktrees on localhost
|
|
// don't overwrite each other's session cookies.
|
|
...(config.IS_DEV && process.env.HDX_DEV_SLOT
|
|
? { name: `connect.sid.${process.env.HDX_DEV_SLOT}` }
|
|
: {}),
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
secret: config.EXPRESS_SESSION_SECRET,
|
|
cookie: {
|
|
secure: false,
|
|
sameSite: 'lax',
|
|
maxAge: 1000 * 60 * 60 * 24 * 30, // 30 days
|
|
},
|
|
rolling: true,
|
|
store: new MongoStore({ mongoUrl: config.MONGO_URI }),
|
|
};
|
|
|
|
app.set('trust proxy', 1);
|
|
if (!config.IS_CI && config.FRONTEND_URL) {
|
|
const feUrl = new URL(config.FRONTEND_URL);
|
|
sess.cookie.domain = feUrl.hostname;
|
|
if (feUrl.protocol === 'https:') {
|
|
sess.cookie.secure = true;
|
|
}
|
|
}
|
|
|
|
app.disable('x-powered-by');
|
|
app.use(compression());
|
|
app.use(express.json({ limit: '32mb' }));
|
|
app.use(express.text({ limit: '32mb' }));
|
|
app.use(express.urlencoded({ extended: false, limit: '32mb' }));
|
|
app.use(session(sess));
|
|
|
|
if (!config.IS_LOCAL_APP_MODE) {
|
|
app.use(passport.initialize());
|
|
app.use(passport.session());
|
|
}
|
|
|
|
if (!config.IS_CI) {
|
|
app.use(expressLogger);
|
|
}
|
|
// Allows timing data from frontend package
|
|
// see: https://github.com/expressjs/cors/issues/102
|
|
app.use(function (req, res, next) {
|
|
onHeaders(res, function () {
|
|
const allowOrigin = res.getHeader('Access-Control-Allow-Origin');
|
|
if (allowOrigin) {
|
|
res.setHeader('Timing-Allow-Origin', allowOrigin);
|
|
}
|
|
});
|
|
next();
|
|
});
|
|
app.use(defaultCors);
|
|
|
|
// ---------------------------------------------------------------------
|
|
// ----------------------- Background Jobs -----------------------------
|
|
// ---------------------------------------------------------------------
|
|
if (config.USAGE_STATS_ENABLED && !config.IS_CI) {
|
|
usageStats();
|
|
}
|
|
// ---------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------
|
|
// ----------------------- Internal Routers ----------------------------
|
|
// ---------------------------------------------------------------------
|
|
// PUBLIC ROUTES
|
|
app.use('/', routers.rootRouter);
|
|
|
|
// SELF-AUTHENTICATED ROUTES (validated via access key, not session middleware)
|
|
app.use('/mcp', mcpRouter);
|
|
|
|
// PRIVATE ROUTES
|
|
app.use('/ai', isUserAuthenticated, routers.aiRouter);
|
|
app.use('/alerts', isUserAuthenticated, routers.alertsRouter);
|
|
app.use('/dashboards', isUserAuthenticated, routers.dashboardRouter);
|
|
app.use('/me', isUserAuthenticated, routers.meRouter);
|
|
app.use('/team', isUserAuthenticated, routers.teamRouter);
|
|
app.use('/webhooks', isUserAuthenticated, routers.webhooksRouter);
|
|
app.use('/connections', isUserAuthenticated, connectionsRouter);
|
|
app.use('/sources', isUserAuthenticated, sourcesRouter);
|
|
app.use('/saved-search', isUserAuthenticated, savedSearchRouter);
|
|
app.use('/favorites', isUserAuthenticated, favoritesRouter);
|
|
app.use('/clickhouse-proxy', isUserAuthenticated, clickhouseProxyRouter);
|
|
// ---------------------------------------------------------------------
|
|
|
|
// TODO: Separate external API routers from internal routers
|
|
// ---------------------------------------------------------------------
|
|
// ----------------------- External Routers ----------------------------
|
|
// ---------------------------------------------------------------------
|
|
// API v2
|
|
// Only initialize Swagger in development or if explicitly enabled
|
|
if (
|
|
process.env.NODE_ENV !== 'production' &&
|
|
process.env.ENABLE_SWAGGER === 'true'
|
|
) {
|
|
// Will require a refactor to ESM to use import statements
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
const { setupSwagger } = require('./utils/swagger');
|
|
setupSwagger(app);
|
|
logger.info('Swagger UI setup and available at /api/v2/docs');
|
|
}
|
|
|
|
app.use('/api/v2', externalRoutersV2);
|
|
|
|
// error handling
|
|
app.use(appErrorHandler);
|
|
|
|
export default app;
|