Files
Dorod-Sky/skyvern-ts/client/tests/mock-server/mockEndpointBuilder.ts
2025-10-23 20:14:59 -06:00

216 lines
6.7 KiB
TypeScript

import { type DefaultBodyType, type HttpHandler, HttpResponse, type HttpResponseResolver, http } from "msw";
import { url } from "../../src/core";
import { toJson } from "../../src/core/json";
import { withHeaders } from "./withHeaders";
import { withJson } from "./withJson";
type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head";
interface MethodStage {
baseUrl(baseUrl: string): MethodStage;
all(path: string): RequestHeadersStage;
get(path: string): RequestHeadersStage;
post(path: string): RequestHeadersStage;
put(path: string): RequestHeadersStage;
delete(path: string): RequestHeadersStage;
patch(path: string): RequestHeadersStage;
options(path: string): RequestHeadersStage;
head(path: string): RequestHeadersStage;
}
interface RequestHeadersStage extends RequestBodyStage, ResponseStage {
header(name: string, value: string): RequestHeadersStage;
headers(headers: Record<string, string>): RequestBodyStage;
}
interface RequestBodyStage extends ResponseStage {
jsonBody(body: unknown): ResponseStage;
}
interface ResponseStage {
respondWith(): ResponseStatusStage;
}
interface ResponseStatusStage {
statusCode(statusCode: number): ResponseHeaderStage;
}
interface ResponseHeaderStage extends ResponseBodyStage, BuildStage {
header(name: string, value: string): ResponseHeaderStage;
headers(headers: Record<string, string>): ResponseHeaderStage;
}
interface ResponseBodyStage {
jsonBody(body: unknown): BuildStage;
}
interface BuildStage {
build(): HttpHandler;
}
export interface HttpHandlerBuilderOptions {
onBuild?: (handler: HttpHandler) => void;
once?: boolean;
}
class RequestBuilder implements MethodStage, RequestHeadersStage, RequestBodyStage, ResponseStage {
private method: HttpMethod = "get";
private _baseUrl: string = "";
private path: string = "/";
private readonly predicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[] = [];
private readonly handlerOptions?: HttpHandlerBuilderOptions;
constructor(options?: HttpHandlerBuilderOptions) {
this.handlerOptions = options;
}
baseUrl(baseUrl: string): MethodStage {
this._baseUrl = baseUrl;
return this;
}
all(path: string): RequestHeadersStage {
this.method = "all";
this.path = path;
return this;
}
get(path: string): RequestHeadersStage {
this.method = "get";
this.path = path;
return this;
}
post(path: string): RequestHeadersStage {
this.method = "post";
this.path = path;
return this;
}
put(path: string): RequestHeadersStage {
this.method = "put";
this.path = path;
return this;
}
delete(path: string): RequestHeadersStage {
this.method = "delete";
this.path = path;
return this;
}
patch(path: string): RequestHeadersStage {
this.method = "patch";
this.path = path;
return this;
}
options(path: string): RequestHeadersStage {
this.method = "options";
this.path = path;
return this;
}
head(path: string): RequestHeadersStage {
this.method = "head";
this.path = path;
return this;
}
header(name: string, value: string): RequestHeadersStage {
this.predicates.push((resolver) => withHeaders({ [name]: value }, resolver));
return this;
}
headers(headers: Record<string, string>): RequestBodyStage {
this.predicates.push((resolver) => withHeaders(headers, resolver));
return this;
}
jsonBody(body: unknown): ResponseStage {
if (body === undefined) {
throw new Error("Undefined is not valid JSON. Do not call jsonBody if you want an empty body.");
}
this.predicates.push((resolver) => withJson(body, resolver));
return this;
}
respondWith(): ResponseStatusStage {
return new ResponseBuilder(this.method, this.buildUrl(), this.predicates, this.handlerOptions);
}
private buildUrl(): string {
return url.join(this._baseUrl, this.path);
}
}
class ResponseBuilder implements ResponseStatusStage, ResponseHeaderStage, ResponseBodyStage, BuildStage {
private readonly method: HttpMethod;
private readonly url: string;
private readonly requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[];
private readonly handlerOptions?: HttpHandlerBuilderOptions;
private responseStatusCode: number = 200;
private responseHeaders: Record<string, string> = {};
private responseBody: DefaultBodyType = undefined;
constructor(
method: HttpMethod,
url: string,
requestPredicates: ((resolver: HttpResponseResolver) => HttpResponseResolver)[],
options?: HttpHandlerBuilderOptions,
) {
this.method = method;
this.url = url;
this.requestPredicates = requestPredicates;
this.handlerOptions = options;
}
public statusCode(code: number): ResponseHeaderStage {
this.responseStatusCode = code;
return this;
}
public header(name: string, value: string): ResponseHeaderStage {
this.responseHeaders[name] = value;
return this;
}
public headers(headers: Record<string, string>): ResponseHeaderStage {
this.responseHeaders = { ...this.responseHeaders, ...headers };
return this;
}
public jsonBody(body: unknown): BuildStage {
if (body === undefined) {
throw new Error("Undefined is not valid JSON. Do not call jsonBody if you expect an empty body.");
}
this.responseBody = toJson(body);
return this;
}
public build(): HttpHandler {
const responseResolver: HttpResponseResolver = () => {
const response = new HttpResponse(this.responseBody, {
status: this.responseStatusCode,
headers: this.responseHeaders,
});
// if no Content-Type header is set, delete the default text content type that is set
if (Object.keys(this.responseHeaders).some((key) => key.toLowerCase() === "content-type") === false) {
response.headers.delete("Content-Type");
}
return response;
};
const finalResolver = this.requestPredicates.reduceRight((acc, predicate) => predicate(acc), responseResolver);
const handler = http[this.method](this.url, finalResolver, this.handlerOptions);
this.handlerOptions?.onBuild?.(handler);
return handler;
}
}
export function mockEndpointBuilder(options?: HttpHandlerBuilderOptions): MethodStage {
return new RequestBuilder(options);
}