feat(scenario): run logs, lint/format tooling, CONTRIBUTING

- add ScenarioRunLogEntity to persist step script output to DB
- stepLogger dual-writes to NestJS logger and DB (fire-and-forget)
- add GET /scenarios/:id/run/:runId returning run, stepRuns and logs
- add POST /scenarios/:id/run/:runId/wait (polls until terminal state)
- 9 new integration tests for the two endpoints (136 total)
- add eslint with typescript-eslint and eslint-config-prettier
- add npm scripts: format, lint, lint:fix
- resolve all lint errors across src and test (no any types)
- add CONTRIBUTING.md covering dev workflow
This commit is contained in:
2026-04-08 18:10:42 +03:00
parent b0c02a1cdc
commit 9a9ccbfe37
67 changed files with 3809 additions and 1412 deletions
+39 -33
View File
@@ -1,23 +1,27 @@
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';
import debug from 'debug';
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");
jest.mock('@nestjs/common', () => {
const actual = jest.requireActual('@nestjs/common');
const log = require('debug')('test');
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] : []));
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) {
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';
const ctx = context ?? this.context ?? "App";
log(`[${ctx}]`, level, message);
};
}
@@ -25,29 +29,30 @@ jest.mock('@nestjs/common', () => {
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 { HealthController } from '../src/health/health.controller';
import { HttpExceptionFilter } from '../src/filters/http-exception.filter';
import { LoggingInterceptor } from '../src/interceptors/logging.interceptor';
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";
export async function buildTestApp(): Promise<INestApplication> {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({ isGlobal: true, ignoreEnvFile: true }),
TypeOrmModule.forRoot({
type: 'better-sqlite3',
database: ':memory:',
type: "better-sqlite3",
database: ":memory:",
entities: [
SessionEntity,
EnvironmentEntity,
@@ -55,6 +60,7 @@ export async function buildTestApp(): Promise<INestApplication> {
ScenarioStepEntity,
ScenarioRunEntity,
ScenarioRunStepEntity,
ScenarioRunLogEntity,
],
synchronize: true,
}),