- SessionContextService: in-memory Map of live Playwright handles, reused per sessionName across exec_code/open_url calls; closed on module destroy - SessionSchedulerService: @Interval closes idle sessions and deletes old closed ones using SESSION_IDLE_TIMEOUT_MINUTES / SESSION_DELETE_CLOSED_DAYS - SessionService: onApplicationBootstrap closes all open sessions on restart; upsert marks status=open and sets lastUsedAt; adds markOpen/markClosed/ touchLastUsed/findExpiredOpen/findOldClosed/findById helpers - SessionEntity: status (open|closed) and lastUsedAt columns added - AuthService: keeps browser alive after login, registers context in pool - BrowserService: named sessions reuse persistent context; anonymous remain ephemeral - AppConfig: all config fields declared with typed defaults; validate wired into ConfigModule so mis-configuration fails fast at startup - ConfigService<AppConfig, true> used everywhere — no more untyped get() calls
90 lines
3.3 KiB
TypeScript
90 lines
3.3 KiB
TypeScript
import { INestApplication, ValidationPipe } from "@nestjs/common";
|
|
import { Test, TestingModule } from "@nestjs/testing";
|
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
|
import { ConfigModule } from "@nestjs/config";
|
|
import { ScheduleModule } from "@nestjs/schedule";
|
|
jest.mock("@nestjs/common", () => {
|
|
const actual = jest.requireActual("@nestjs/common");
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
const log = require("debug")("test");
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
const { Logger } = require("@nestjs/common/services/logger.service");
|
|
|
|
Logger.prototype.error = function (
|
|
message: unknown,
|
|
stack?: string,
|
|
context?: string,
|
|
) {
|
|
const ctx = context ?? this.context ?? "App";
|
|
log(`[${ctx}]`, "error", message, ...(stack ? [stack] : []));
|
|
};
|
|
|
|
for (const level of ["log", "warn", "debug", "verbose", "fatal"] as const) {
|
|
Logger.prototype[level] = function (message: unknown, context?: string) {
|
|
const ctx = context ?? this.context ?? "App";
|
|
log(`[${ctx}]`, level, message);
|
|
};
|
|
}
|
|
|
|
return actual;
|
|
});
|
|
|
|
import { AuthModule } from "../src/auth/auth.module";
|
|
import { BrowserModule } from "../src/browser/browser.module";
|
|
import { SessionModule } from "../src/session/session.module";
|
|
import { EnvironmentModule } from "../src/environment/environment.module";
|
|
import { ScenarioModule } from "../src/scenario/scenario.module";
|
|
import { McpModule } from "../src/mcp/mcp.module";
|
|
import { SessionEntity } from "../src/session/session.entity";
|
|
import { EnvironmentEntity } from "../src/environment/environment.entity";
|
|
import { ScenarioEntity } from "../src/scenario/scenario.entity";
|
|
import { ScenarioStepEntity } from "../src/scenario/scenario-step.entity";
|
|
import { ScenarioRunEntity } from "../src/scenario/scenario-run.entity";
|
|
import { ScenarioRunStepEntity } from "../src/scenario/scenario-run-step.entity";
|
|
import { ScenarioRunLogEntity } from "../src/scenario/scenario-run-log.entity";
|
|
import { HealthController } from "../src/health/health.controller";
|
|
import { HttpExceptionFilter } from "../src/filters/http-exception.filter";
|
|
import { LoggingInterceptor } from "../src/interceptors/logging.interceptor";
|
|
import { validateAppConfig } from "../src/config/app.config";
|
|
|
|
export async function buildTestApp(): Promise<INestApplication> {
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
imports: [
|
|
ConfigModule.forRoot({
|
|
isGlobal: true,
|
|
ignoreEnvFile: true,
|
|
validate: validateAppConfig,
|
|
}),
|
|
TypeOrmModule.forRoot({
|
|
type: "better-sqlite3",
|
|
database: ":memory:",
|
|
entities: [
|
|
SessionEntity,
|
|
EnvironmentEntity,
|
|
ScenarioEntity,
|
|
ScenarioStepEntity,
|
|
ScenarioRunEntity,
|
|
ScenarioRunStepEntity,
|
|
ScenarioRunLogEntity,
|
|
],
|
|
synchronize: true,
|
|
}),
|
|
ScheduleModule.forRoot(),
|
|
AuthModule,
|
|
BrowserModule,
|
|
SessionModule,
|
|
EnvironmentModule,
|
|
ScenarioModule,
|
|
McpModule,
|
|
],
|
|
controllers: [HealthController],
|
|
}).compile();
|
|
|
|
const app = module.createNestApplication();
|
|
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
|
|
app.useGlobalFilters(new HttpExceptionFilter());
|
|
app.useGlobalInterceptors(new LoggingInterceptor());
|
|
await app.init();
|
|
return app;
|
|
}
|