feat(browser): add environment, credentials, and script logger to POST /exec
- ExecDto gains optional `environment` and `credentials` fields - BrowserService.exec forwards both to CodeExecutorService.execute so helpers.env, helpers.getEnvUrl(), and helpers.getCredential() work identically to scenario-scheduler steps - script console.log/warn/error now routed through TraceLogger with session label prefix - integration tests cover env/creds injection and missing-alias error
This commit is contained in:
@@ -56,6 +56,6 @@ export class BrowserController {
|
||||
@ApiResponse({ status: 500, description: "Execution failed" })
|
||||
exec(@Body() dto: ExecDto): Promise<ExecResult> {
|
||||
this.codeExecutor.validate(dto.code);
|
||||
return this.browserService.exec(dto.sessionName, dto.code, dto.url);
|
||||
return this.browserService.exec(dto.sessionName, dto.code, dto.url, dto.environment, dto.credentials);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ import { JSDOM } from "jsdom";
|
||||
import { SessionContextService } from "../session/session-context.service";
|
||||
import { SessionService } from "../session/session.service";
|
||||
import { CodeExecutorService } from "../code-executor/code-executor.service";
|
||||
import type { ExecResult } from "../code-executor/code-executor.service";
|
||||
import type { ExecResult, ScriptLogger } from "../code-executor/code-executor.service";
|
||||
import { SnippetService } from "../snippet/snippet.service";
|
||||
import type { Cookie } from "playwright";
|
||||
import type { EnvironmentData } from "../environment/environment.entity";
|
||||
|
||||
export type { ExecResult } from "../code-executor/code-executor.service";
|
||||
|
||||
@@ -166,6 +167,8 @@ export class BrowserService {
|
||||
sessionName: string | undefined,
|
||||
code: string,
|
||||
url?: string,
|
||||
environment?: EnvironmentData,
|
||||
credentials?: Record<string, unknown>,
|
||||
): Promise<ExecResult> {
|
||||
const label = sessionName ?? "anonymous";
|
||||
|
||||
@@ -182,14 +185,16 @@ export class BrowserService {
|
||||
await page.goto(url, { waitUntil: "networkidle" });
|
||||
}
|
||||
this.logger.log(`[${label}] exec: running user code`);
|
||||
const scriptLogger: ScriptLogger = (level, msg) =>
|
||||
this.logger[level](`[${label}] script: ${msg}`);
|
||||
const result = await this.codeExecutor.execute(
|
||||
page,
|
||||
context,
|
||||
code,
|
||||
scriptLogger,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
credentials,
|
||||
environment,
|
||||
snippets,
|
||||
);
|
||||
this.logger.log(`[${label}] exec: done`);
|
||||
@@ -217,14 +222,16 @@ export class BrowserService {
|
||||
}
|
||||
|
||||
this.logger.log(`[${label}] exec: running user code`);
|
||||
const scriptLogger: ScriptLogger = (level, msg) =>
|
||||
this.logger[level](`[${label}] script: ${msg}`);
|
||||
const result = await this.codeExecutor.execute(
|
||||
page,
|
||||
context,
|
||||
code,
|
||||
scriptLogger,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
credentials,
|
||||
environment,
|
||||
snippetMap,
|
||||
);
|
||||
this.logger.log(`[${label}] exec: done`);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsOptional, IsString, IsUrl } from "class-validator";
|
||||
import { IsObject, IsOptional, IsString, IsUrl } from "class-validator";
|
||||
import type { EnvironmentData } from "../../environment/environment.entity";
|
||||
|
||||
export class ExecDto {
|
||||
@ApiPropertyOptional({
|
||||
@@ -27,4 +28,22 @@ export class ExecDto {
|
||||
})
|
||||
@IsString()
|
||||
code: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
"Key/value map of environment variables available via `helpers.env` and `helpers.getEnvUrl()` in the script.",
|
||||
example: { BASE_URL: "https://example.com" },
|
||||
})
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
environment?: EnvironmentData;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
"Key/value map of credentials available via `helpers.getCredential()` in the script.",
|
||||
example: { admin: { username: "user", password: "pass" } },
|
||||
})
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
credentials?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
@@ -135,5 +135,74 @@ describe("BrowserController", () => {
|
||||
.expect(201);
|
||||
expect(res.body).toEqual({ result: 42 });
|
||||
});
|
||||
|
||||
it("exposes environment via helpers.env in a named session", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post("/exec")
|
||||
.send({
|
||||
sessionName: "no-such-session",
|
||||
code: "return helpers.env.BASE_URL;",
|
||||
environment: { BASE_URL: "https://env.example.com" },
|
||||
})
|
||||
.expect(201);
|
||||
expect(res.body).toEqual({ result: "https://env.example.com" });
|
||||
});
|
||||
|
||||
it("exposes environment via helpers.env in a sessionless exec", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post("/exec")
|
||||
.send({
|
||||
code: "return helpers.env.KEY;",
|
||||
environment: { KEY: "value123" },
|
||||
})
|
||||
.expect(201);
|
||||
expect(res.body).toEqual({ result: "value123" });
|
||||
});
|
||||
|
||||
it("exposes credentials via helpers.getCredential in a named session", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post("/exec")
|
||||
.send({
|
||||
sessionName: "no-such-session",
|
||||
code: "return helpers.getCredential('admin');",
|
||||
credentials: { admin: { username: "user1", password: "pass1" } },
|
||||
})
|
||||
.expect(201);
|
||||
expect(res.body).toEqual({
|
||||
result: { username: "user1", password: "pass1" },
|
||||
});
|
||||
});
|
||||
|
||||
it("exposes credentials via helpers.getCredential in a sessionless exec", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post("/exec")
|
||||
.send({
|
||||
code: "return helpers.getCredential('svc');",
|
||||
credentials: { svc: { token: "abc" } },
|
||||
})
|
||||
.expect(201);
|
||||
expect(res.body).toEqual({ result: { token: "abc" } });
|
||||
});
|
||||
|
||||
it("throws when getCredential is called with an unknown alias", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post("/exec")
|
||||
.send({
|
||||
code: "return helpers.getCredential('missing');",
|
||||
credentials: {},
|
||||
})
|
||||
.expect(500);
|
||||
expect(res.body.message).toMatch(/missing/);
|
||||
});
|
||||
|
||||
it("environment and credentials default to empty when omitted", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post("/exec")
|
||||
.send({
|
||||
code: "return Object.keys(helpers.env).length === 0 ? 'empty' : 'not-empty';",
|
||||
})
|
||||
.expect(201);
|
||||
expect(res.body).toEqual({ result: "empty" });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user