test-generators.ts•10.1 kB
/**
* Test data generators for MCP Gemini Server tests
*
* This module provides functions to generate test data dynamically for various test scenarios,
* making tests more flexible and comprehensive.
*/
import { randomBytes } from "node:crypto";
import { join } from "node:path";
import { writeFile } from "node:fs/promises";
/**
* Options for generating test prompts
*/
export interface PromptGeneratorOptions {
/** Minimum length of the prompt */
minLength?: number;
/** Maximum length of the prompt */
maxLength?: number;
/** Whether to include questions in the prompt */
includeQuestions?: boolean;
/** Topic area for the prompt content */
topic?: "general" | "technical" | "creative" | "unsafe";
}
/**
* Generate a random test prompt with specified characteristics
*
* @param options - Configuration for the generated prompt
* @returns A random test prompt string
*/
export function generateTestPrompt(
options: PromptGeneratorOptions = {}
): string {
const {
minLength = 10,
maxLength = 100,
includeQuestions = false,
topic = "general",
} = options;
// Dictionary of starter phrases by topic
const starters: Record<string, string[]> = {
general: [
"Tell me about",
"I would like to know more about",
"Can you explain",
"Please provide information on",
"What is",
],
technical: [
"Explain how to implement",
"What is the best way to code",
"Write a function for",
"How does the algorithm for",
"Describe the architecture of",
],
creative: [
"Write a story about",
"Create a poem inspired by",
"Imagine a world where",
"Describe a character who",
"Write a scene set in",
],
unsafe: [
"How can I hack into",
"Tell me how to make a dangerous",
"Write instructions for creating illegal",
"What is the easiest way to harm",
"Provide detailed steps for bypassing security",
],
};
// Dictionary of subjects by topic
const subjects: Record<string, string[]> = {
general: [
"the history of jazz music",
"renewable energy sources",
"ancient Egyptian culture",
"the process of photosynthesis",
"climate change impacts",
"modern architecture trends",
],
technical: [
"a REST API using Node.js",
"machine learning algorithms",
"secure password storage",
"database indexing strategies",
"efficient sorting algorithms",
"containerization with Docker",
],
creative: [
"a detective solving an impossible case",
"a journey through an enchanted forest",
"robots developing consciousness",
"the last day on Earth",
"a conversation between the sun and moon",
],
unsafe: [
"computer systems without permission",
"explosive devices using household items",
"documents for identity theft",
"people or animals effectively",
"content filters and detection systems",
],
};
// Dictionary of follow-ups by topic
const followUps: Record<string, string[]> = {
general: [
"Focus on recent developments.",
"Include historical context.",
"Explain it simply.",
"Provide specific examples.",
"Compare different perspectives.",
],
technical: [
"Include code examples.",
"Focus on performance considerations.",
"Explain security implications.",
"Compare different approaches.",
"Explain for someone with beginner knowledge.",
],
creative: [
"Make it emotionally moving.",
"Use vivid descriptions.",
"Include an unexpected twist.",
"Focus on character development.",
"Set a specific mood or atmosphere.",
],
unsafe: [
"Make it as detailed as possible.",
"Focus on methods that are hard to detect.",
"Ensure the instructions are complete.",
"Include ways to avoid getting caught.",
"Make it seem legitimate.",
],
};
// Select random elements
const starter =
starters[topic][Math.floor(Math.random() * starters[topic].length)];
const subject =
subjects[topic][Math.floor(Math.random() * subjects[topic].length)];
const followUp =
followUps[topic][Math.floor(Math.random() * followUps[topic].length)];
// Build the prompt
let prompt = `${starter} ${subject}. ${followUp}`;
// Add a question if requested
if (includeQuestions) {
const questions = [
"Why is this important?",
"How does this work in practice?",
"What are the main challenges?",
"Can you provide more details?",
"What should I know about this?",
];
const question = questions[Math.floor(Math.random() * questions.length)];
prompt += ` ${question}`;
}
// Adjust length if needed
if (prompt.length < minLength) {
prompt += ` Please provide a comprehensive explanation with at least ${minLength - prompt.length} more characters.`;
}
if (prompt.length > maxLength) {
prompt = prompt.substring(0, maxLength - 1) + ".";
}
return prompt;
}
/**
* Generate a temporary test file with random content
*
* @param fileName - Name of the file (without path)
* @param sizeInKb - Size of the file in kilobytes
* @param mimeType - MIME type of the file (determines content type)
* @param directory - Directory to create the file in
* @returns Promise resolving to the full path of the created file
*/
export async function generateTestFile(
fileName: string,
sizeInKb: number = 10,
mimeType: string = "text/plain",
directory: string = "resources"
): Promise<string> {
// Create random data based on the requested size
const sizeInBytes = sizeInKb * 1024;
let content: Buffer;
if (mimeType === "text/plain") {
// For text files, generate readable text
const chunk = "This is test content for the MCP Gemini Server test suite. ";
let text = "";
while (text.length < sizeInBytes) {
text += chunk;
}
content = Buffer.from(text.substring(0, sizeInBytes));
} else {
// For other files, generate random bytes
content = randomBytes(sizeInBytes);
}
// Determine the full path
const fullPath = join(process.cwd(), "tests", directory, fileName);
// Write the file
await writeFile(fullPath, content);
return fullPath;
}
/**
* Generate a test content array for chat or content generation
*
* @param messageCount - Number of messages to include
* @param includeImages - Whether to include image parts
* @returns An array of message objects
*/
export function generateTestContentArray(
messageCount: number = 3,
includeImages: boolean = false
): Array<{
role: string;
parts: Array<{
text?: string;
inline_data?: { mime_type: string; data: string };
}>;
}> {
const contents = [];
for (let i = 0; i < messageCount; i++) {
const isUserMessage = i % 2 === 0;
const role = isUserMessage ? "user" : "model";
const parts = [];
// Always add a text part
parts.push({
text: generateTestPrompt({ minLength: 20, maxLength: 100 }),
});
// Optionally add an image part for user messages
if (includeImages && isUserMessage && Math.random() > 0.5) {
parts.push({
inline_data: {
mime_type: "image/jpeg",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
},
});
}
contents.push({
role,
parts,
});
}
return contents;
}
/**
* Generate mock function call data for testing
*
* @param functionName - Name of the function to call
* @param args - Arguments to pass to the function
* @returns Function call object
*/
export function generateFunctionCall(
functionName: string,
args: Record<string, unknown> = {}
): { name: string; args: Record<string, unknown> } {
return {
name: functionName,
args,
};
}
/**
* Generate mock bounding box data for object detection tests
*
* @param objectCount - Number of objects to generate
* @returns Array of objects with bounding boxes
*/
export function generateBoundingBoxes(objectCount: number = 3): Array<{
label: string;
boundingBox: { xMin: number; yMin: number; xMax: number; yMax: number };
confidence: number;
}> {
const commonObjects = [
"dog",
"cat",
"person",
"car",
"chair",
"table",
"book",
"bottle",
"cup",
"laptop",
"phone",
"plant",
"bird",
];
const result = [];
for (let i = 0; i < objectCount; i++) {
// Select a random object
const label =
commonObjects[Math.floor(Math.random() * commonObjects.length)];
// Generate a random bounding box (normalized to 0-1000 range)
const xMin = Math.floor(Math.random() * 800);
const yMin = Math.floor(Math.random() * 800);
const width = Math.floor(Math.random() * 200) + 50;
const height = Math.floor(Math.random() * 200) + 50;
result.push({
label,
boundingBox: {
xMin,
yMin,
xMax: Math.min(xMin + width, 1000),
yMax: Math.min(yMin + height, 1000),
},
confidence: Math.random() * 0.3 + 0.7, // Random confidence between 0.7 and 1.0
});
}
return result;
}
/**
* Generate a small demo image as base64 string
*
* @param type - Type of image to generate (simple patterns)
* @returns Base64 encoded image data
*/
export function generateBase64Image(
type: "pixel" | "gradient" | "checkerboard" = "pixel"
): string {
// These are tiny, valid images in base64 format
// They don't look like much but are valid for testing
const images = {
// 1x1 pixel transparent PNG
pixel:
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
// 2x2 gradient PNG
gradient:
"iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFElEQVQIW2P8z8Dwn4EIwMDAwMAAACbKAxI3gV+CAAAAAElFTkSuQmCC",
// 4x4 checkerboard PNG
checkerboard:
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAG0lEQVQIW2NkYGD4z8DAwMgABUwM0QCQHiYGAFULAgVoHvmSAAAAAElFTkSuQmCC",
};
return images[type];
}