Update test framework: fix run_tests.py to support all test files, add auto-import-check for test files

This commit is contained in:
qiaoxinjiu
2026-05-09 15:11:30 +08:00
parent eb053a347f
commit eaba8328da
21739 changed files with 2236758 additions and 719 deletions

View File

@@ -0,0 +1,33 @@
import type { APIRequestContext } from 'playwright';
import { ToolHandler, ToolContext, ToolResponse } from '../common/types.js';
/**
* Base class for all API-based tools
* Provides common functionality and error handling
*/
export declare abstract class ApiToolBase implements ToolHandler {
protected server: any;
constructor(server: any);
/**
* Main execution method that all tools must implement
*/
abstract execute(args: any, context: ToolContext): Promise<ToolResponse>;
/**
* Ensures an API context is available and returns it
* @param context The tool context containing apiContext
* @returns The apiContext or null if not available
*/
protected ensureApiContext(context: ToolContext): APIRequestContext | null;
/**
* Validates that an API context is available and returns an error response if not
* @param context The tool context
* @returns Either null if apiContext is available, or an error response
*/
protected validateApiContextAvailable(context: ToolContext): ToolResponse | null;
/**
* Safely executes an API operation with proper error handling
* @param context The tool context
* @param operation The async operation to perform
* @returns The tool response
*/
protected safeExecute(context: ToolContext, operation: (apiContext: APIRequestContext) => Promise<ToolResponse>): Promise<ToolResponse>;
}

View File

@@ -0,0 +1,49 @@
import { createErrorResponse } from '../common/types.js';
/**
* Base class for all API-based tools
* Provides common functionality and error handling
*/
export class ApiToolBase {
constructor(server) {
this.server = server;
}
/**
* Ensures an API context is available and returns it
* @param context The tool context containing apiContext
* @returns The apiContext or null if not available
*/
ensureApiContext(context) {
if (!context.apiContext) {
return null;
}
return context.apiContext;
}
/**
* Validates that an API context is available and returns an error response if not
* @param context The tool context
* @returns Either null if apiContext is available, or an error response
*/
validateApiContextAvailable(context) {
if (!this.ensureApiContext(context)) {
return createErrorResponse("API context not initialized");
}
return null;
}
/**
* Safely executes an API operation with proper error handling
* @param context The tool context
* @param operation The async operation to perform
* @returns The tool response
*/
async safeExecute(context, operation) {
const apiError = this.validateApiContextAvailable(context);
if (apiError)
return apiError;
try {
return await operation(context.apiContext);
}
catch (error) {
return createErrorResponse(`API operation failed: ${error.message}`);
}
}
}

View File

@@ -0,0 +1,2 @@
export * from './base.js';
export * from './requests.js';

View File

@@ -0,0 +1,3 @@
export * from './base.js';
export * from './requests.js';
// TODO: Add exports for other API tools as they are implemented

View File

@@ -0,0 +1,61 @@
import { ApiToolBase } from './base.js';
import { ToolContext, ToolResponse } from '../common/types.js';
/**
* Base arguments for all API requests
*/
export interface BaseRequestArgs {
url: string;
token?: string;
headers?: Record<string, string>;
}
/**
* Arguments for requests with body (POST, PUT, PATCH)
*/
export interface RequestWithBodyArgs extends BaseRequestArgs {
value: string | object;
}
/**
* Tool for making GET requests
*/
export declare class GetRequestTool extends ApiToolBase {
/**
* Execute the GET request tool
*/
execute(args: BaseRequestArgs, context: ToolContext): Promise<ToolResponse>;
}
/**
* Tool for making POST requests
*/
export declare class PostRequestTool extends ApiToolBase {
/**
* Execute the POST request tool
*/
execute(args: RequestWithBodyArgs, context: ToolContext): Promise<ToolResponse>;
}
/**
* Tool for making PUT requests
*/
export declare class PutRequestTool extends ApiToolBase {
/**
* Execute the PUT request tool
*/
execute(args: RequestWithBodyArgs, context: ToolContext): Promise<ToolResponse>;
}
/**
* Tool for making PATCH requests
*/
export declare class PatchRequestTool extends ApiToolBase {
/**
* Execute the PATCH request tool
*/
execute(args: RequestWithBodyArgs, context: ToolContext): Promise<ToolResponse>;
}
/**
* Tool for making DELETE requests
*/
export declare class DeleteRequestTool extends ApiToolBase {
/**
* Execute the DELETE request tool
*/
execute(args: BaseRequestArgs, context: ToolContext): Promise<ToolResponse>;
}

View File

@@ -0,0 +1,252 @@
import { ApiToolBase } from './base.js';
import { createSuccessResponse, createErrorResponse } from '../common/types.js';
/**
* Helper function to safely parse JSON string or return the value as-is
* @param value The value to parse (can be string or object)
* @returns Parsed JSON object or the original value
*/
function parseJsonSafely(value) {
if (typeof value === 'string') {
try {
return JSON.parse(value);
}
catch (error) {
// Log warning for debugging
console.warn('Failed to parse JSON, using raw string:', error instanceof Error ? error.message : 'Unknown error');
return value;
}
}
return value;
}
/**
* Helper function to build request headers with optional token and custom headers
* @param token Optional Bearer token for authorization
* @param customHeaders Optional custom headers to include
* @param includeContentType Whether to include Content-Type: application/json header
* @returns Merged headers object
*/
function buildHeaders(token, customHeaders, includeContentType = false) {
const headers = {};
if (includeContentType) {
headers['Content-Type'] = 'application/json';
}
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
if (customHeaders) {
// Warn if both token and Authorization header are provided
if (token && customHeaders['Authorization']) {
console.warn('Both token and Authorization header provided. Custom Authorization header will override token.');
}
Object.assign(headers, customHeaders);
}
return headers;
}
/**
* Validate headers are all strings
* @param headers Headers to validate
* @returns Error message if invalid, null if valid
*/
function validateHeaders(headers) {
if (!headers)
return null;
for (const [key, value] of Object.entries(headers)) {
if (typeof value !== 'string') {
return `Header '${key}' must be a string, got ${typeof value}`;
}
}
return null;
}
/**
* Tool for making GET requests
*/
export class GetRequestTool extends ApiToolBase {
/**
* Execute the GET request tool
*/
async execute(args, context) {
return this.safeExecute(context, async (apiContext) => {
// Validate headers
const headerError = validateHeaders(args.headers);
if (headerError) {
return createErrorResponse(headerError);
}
const response = await apiContext.get(args.url, {
headers: buildHeaders(args.token, args.headers)
});
let responseText;
try {
responseText = await response.text();
}
catch (error) {
responseText = "Unable to get response text";
}
return createSuccessResponse([
`GET request to ${args.url}`,
`Status: ${response.status()} ${response.statusText()}`,
`Response: ${responseText.substring(0, 1000)}${responseText.length > 1000 ? '...' : ''}`
]);
});
}
}
/**
* Tool for making POST requests
*/
export class PostRequestTool extends ApiToolBase {
/**
* Execute the POST request tool
*/
async execute(args, context) {
return this.safeExecute(context, async (apiContext) => {
// Validate headers
const headerError = validateHeaders(args.headers);
if (headerError) {
return createErrorResponse(headerError);
}
// Check if the value is valid JSON if it starts with { or [
if (args.value && typeof args.value === 'string' &&
(args.value.startsWith('{') || args.value.startsWith('['))) {
try {
JSON.parse(args.value);
}
catch (error) {
return createErrorResponse(`Failed to parse request body: ${error.message}`);
}
}
const response = await apiContext.post(args.url, {
data: parseJsonSafely(args.value),
headers: buildHeaders(args.token, args.headers, true)
});
let responseText;
try {
responseText = await response.text();
}
catch (error) {
responseText = "Unable to get response text";
}
return createSuccessResponse([
`POST request to ${args.url}`,
`Status: ${response.status()} ${response.statusText()}`,
`Response: ${responseText.substring(0, 1000)}${responseText.length > 1000 ? '...' : ''}`
]);
});
}
}
/**
* Tool for making PUT requests
*/
export class PutRequestTool extends ApiToolBase {
/**
* Execute the PUT request tool
*/
async execute(args, context) {
return this.safeExecute(context, async (apiContext) => {
// Validate headers
const headerError = validateHeaders(args.headers);
if (headerError) {
return createErrorResponse(headerError);
}
// Check if the value is valid JSON if it starts with { or [
if (args.value && typeof args.value === 'string' &&
(args.value.startsWith('{') || args.value.startsWith('['))) {
try {
JSON.parse(args.value);
}
catch (error) {
return createErrorResponse(`Failed to parse request body: ${error.message}`);
}
}
const response = await apiContext.put(args.url, {
data: parseJsonSafely(args.value),
headers: buildHeaders(args.token, args.headers, true)
});
let responseText;
try {
responseText = await response.text();
}
catch (error) {
responseText = "Unable to get response text";
}
return createSuccessResponse([
`PUT request to ${args.url}`,
`Status: ${response.status()} ${response.statusText()}`,
`Response: ${responseText.substring(0, 1000)}${responseText.length > 1000 ? '...' : ''}`
]);
});
}
}
/**
* Tool for making PATCH requests
*/
export class PatchRequestTool extends ApiToolBase {
/**
* Execute the PATCH request tool
*/
async execute(args, context) {
return this.safeExecute(context, async (apiContext) => {
// Validate headers
const headerError = validateHeaders(args.headers);
if (headerError) {
return createErrorResponse(headerError);
}
// Check if the value is valid JSON if it starts with { or [
if (args.value && typeof args.value === 'string' &&
(args.value.startsWith('{') || args.value.startsWith('['))) {
try {
JSON.parse(args.value);
}
catch (error) {
return createErrorResponse(`Failed to parse request body: ${error.message}`);
}
}
const response = await apiContext.patch(args.url, {
data: parseJsonSafely(args.value),
headers: buildHeaders(args.token, args.headers, true)
});
let responseText;
try {
responseText = await response.text();
}
catch (error) {
responseText = "Unable to get response text";
}
return createSuccessResponse([
`PATCH request to ${args.url}`,
`Status: ${response.status()} ${response.statusText()}`,
`Response: ${responseText.substring(0, 1000)}${responseText.length > 1000 ? '...' : ''}`
]);
});
}
}
/**
* Tool for making DELETE requests
*/
export class DeleteRequestTool extends ApiToolBase {
/**
* Execute the DELETE request tool
*/
async execute(args, context) {
return this.safeExecute(context, async (apiContext) => {
// Validate headers
const headerError = validateHeaders(args.headers);
if (headerError) {
return createErrorResponse(headerError);
}
const response = await apiContext.delete(args.url, {
headers: buildHeaders(args.token, args.headers)
});
let responseText;
try {
responseText = await response.text();
}
catch (error) {
responseText = "Unable to get response text";
}
return createSuccessResponse([
`DELETE request to ${args.url}`,
`Status: ${response.status()} ${response.statusText()}`,
`Response: ${responseText.substring(0, 1000)}${responseText.length > 1000 ? '...' : ''}`
]);
});
}
}