mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 15:30:16 +03:00
chore: Новый черновик DRAFT, удалить старые docs-v
This commit is contained in:
145
examples/demo-backend/src/common/scenario.interceptor.ts
Normal file
145
examples/demo-backend/src/common/scenario.interceptor.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ConflictException,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
HttpException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
NestInterceptor,
|
||||
UnauthorizedException,
|
||||
} from "@nestjs/common";
|
||||
import type { Request, Response } from "express";
|
||||
import { Observable } from "rxjs";
|
||||
import { delay, map } from "rxjs/operators";
|
||||
import { DEMO_SCENARIOS, type DemoScenario } from "./api.dto";
|
||||
|
||||
function scenarioFromRequest(request: Request): DemoScenario {
|
||||
const value = String(request.headers["x-demo-scenario"] ?? "normal");
|
||||
return DEMO_SCENARIOS.includes(value as DemoScenario)
|
||||
? (value as DemoScenario)
|
||||
: "normal";
|
||||
}
|
||||
|
||||
export function assertNoForcedAuthFailure(request: Request): void {
|
||||
const scenario = scenarioFromRequest(request);
|
||||
if (scenario === "expired-auth") {
|
||||
throw new UnauthorizedException({
|
||||
code: "DEMO_AUTH_EXPIRED",
|
||||
message: "Authentication was expired by the demo scenario.",
|
||||
});
|
||||
}
|
||||
if (scenario === "forbidden") {
|
||||
throw new ForbiddenException({
|
||||
code: "DEMO_FORBIDDEN",
|
||||
message: "Access was denied by the demo scenario.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ScenarioInterceptor implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
||||
const http = context.switchToHttp();
|
||||
const request = http.getRequest<Request>();
|
||||
const response = http.getResponse<Response>();
|
||||
const scenario = scenarioFromRequest(request);
|
||||
response.setHeader("X-Demo-Scenario", scenario);
|
||||
|
||||
if (scenario === "server-error") {
|
||||
throw new InternalServerErrorException({
|
||||
code: "DEMO_SERVER_ERROR",
|
||||
message: "Server error forced by X-Demo-Scenario.",
|
||||
});
|
||||
}
|
||||
|
||||
if (scenario === "rate-limited") {
|
||||
response.setHeader("Retry-After", "3");
|
||||
throw new HttpException(
|
||||
{
|
||||
code: "DEMO_RATE_LIMITED",
|
||||
message: "Rate limit forced by X-Demo-Scenario.",
|
||||
},
|
||||
429,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
scenario === "conflict" &&
|
||||
!["GET", "HEAD", "OPTIONS"].includes(request.method)
|
||||
) {
|
||||
throw new ConflictException({
|
||||
code: "DEMO_CONFLICT",
|
||||
message: "Conflict forced by X-Demo-Scenario.",
|
||||
});
|
||||
}
|
||||
|
||||
const configuredDelay =
|
||||
scenario === "slow"
|
||||
? Number(process.env.MOCK_SLOW_DELAY_MS ?? 1500)
|
||||
: scenario === "timeout"
|
||||
? Number(process.env.MOCK_TIMEOUT_DELAY_MS ?? 30000)
|
||||
: 0;
|
||||
|
||||
return next.handle().pipe(
|
||||
configuredDelay > 0 ? delay(configuredDelay) : (source) => source,
|
||||
map((payload) => this.transformPayload(payload, scenario)),
|
||||
);
|
||||
}
|
||||
|
||||
private transformPayload(payload: unknown, scenario: DemoScenario): unknown {
|
||||
if (!payload || typeof payload !== "object" || !("data" in payload)) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
const response = payload as {
|
||||
data: unknown;
|
||||
meta?: Record<string, unknown>;
|
||||
};
|
||||
if (!Array.isArray(response.data)) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
if (scenario === "empty") {
|
||||
return {
|
||||
...response,
|
||||
data: [],
|
||||
meta: response.meta
|
||||
? {
|
||||
...response.meta,
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
nextCursor: null,
|
||||
hasMore: false,
|
||||
}
|
||||
: response.meta,
|
||||
};
|
||||
}
|
||||
|
||||
if (scenario === "large-dataset" && response.data.length > 0) {
|
||||
const source = response.data as Array<Record<string, unknown>>;
|
||||
const data = Array.from({ length: 250 }, (_, index) => {
|
||||
const original = source[index % source.length];
|
||||
return {
|
||||
...original,
|
||||
id: `${String(original.id ?? "item")}-scenario-${String(index + 1).padStart(3, "0")}`,
|
||||
};
|
||||
});
|
||||
return {
|
||||
...response,
|
||||
data,
|
||||
meta: response.meta
|
||||
? {
|
||||
...response.meta,
|
||||
total: data.length,
|
||||
totalPages: 1,
|
||||
nextCursor: null,
|
||||
hasMore: false,
|
||||
}
|
||||
: response.meta,
|
||||
};
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user