Skip to main content
Glama
test-base.ts5.57 kB
/** * Integration Test Base Class * * Provides common functionality for integration tests including: * - Automatic namespace lifecycle management * - Kubernetes client setup * - HTTP client for REST API calls * - Common utilities and wait conditions */ import { HttpRestApiClient } from './http-client.js'; import * as k8s from '@kubernetes/client-node'; export class IntegrationTest { public namespace: string = ''; protected k8sApi: k8s.CoreV1Api; public httpClient: HttpRestApiClient; private kc: k8s.KubeConfig; constructor() { // Initialize Kubernetes client with test kubeconfig this.kc = new k8s.KubeConfig(); this.kc.loadFromDefault(); this.k8sApi = this.kc.makeApiClient(k8s.CoreV1Api); // Initialize HTTP client for REST API calls with auth header if configured const authToken = process.env.DOT_AI_AUTH_TOKEN; const headers: Record<string, string> = {}; if (authToken) { headers['Authorization'] = `Bearer ${authToken}`; } this.httpClient = new HttpRestApiClient({ headers }); } /** * Create a pod in the test namespace */ async createPod(name: string, spec: k8s.V1PodSpec): Promise<k8s.V1Pod> { const pod: k8s.V1Pod = { metadata: { name, namespace: this.namespace, }, spec, }; const response = await this.k8sApi.createNamespacedPod({ namespace: this.namespace, body: pod }); return response; } /** * Wait for a pod to reach a specific condition */ async waitForPodCondition( podName: string, condition: 'Running' | 'Failed' | 'Succeeded', timeoutMs: number = 30000 ): Promise<k8s.V1Pod> { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { try { const pod = await this.k8sApi.readNamespacedPod({ name: podName, namespace: this.namespace }); if (pod.status?.phase === condition) { return pod; } // Check for CrashLoopBackOff condition if (condition === 'Failed' && pod.status?.containerStatuses) { const crashLooping = pod.status.containerStatuses.some( (status: any) => status.state?.waiting?.reason === 'CrashLoopBackOff' ); if (crashLooping) { return pod; } } } catch (error) { // Pod might not exist yet, continue waiting } await this.sleep(1000); // Wait 1 second before next check } throw new Error(`Timeout waiting for pod ${podName} to reach condition ${condition}`); } /** * Wait for a deployment to be ready */ async waitForDeploymentReady(deploymentName: string, timeoutMs: number = 60000): Promise<void> { const appsV1Api = this.kc.makeApiClient(k8s.AppsV1Api); const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { try { const deployment = await appsV1Api.readNamespacedDeployment({ name: deploymentName, namespace: this.namespace }); if (deployment.status?.readyReplicas && deployment.status.readyReplicas > 0) { return; } } catch (error) { // Deployment might not exist yet, continue waiting } await this.sleep(2000); // Wait 2 seconds before next check } throw new Error(`Timeout waiting for deployment ${deploymentName} to be ready`); } /** * Create a pod with missing ConfigMap dependency for testing */ async createPodWithMissingConfigMap(podName: string): Promise<k8s.V1Pod> { const spec: k8s.V1PodSpec = { containers: [ { name: 'test-container', image: 'nginx:alpine', env: [ { name: 'CONFIG_VALUE', valueFrom: { configMapKeyRef: { name: 'missing-configmap', key: 'config-key', }, }, }, ], }, ], restartPolicy: 'Always', }; return this.createPod(podName, spec); } /** * Get all pods in the test namespace */ async getPods(): Promise<k8s.V1Pod[]> { const response = await this.k8sApi.listNamespacedPod({ namespace: this.namespace }); return response.items || []; } /** * Execute kubectl command using the test kubeconfig * @param command - kubectl command without the "kubectl" prefix * @returns stdout from the kubectl command */ async kubectl(command: string): Promise<string> { const { execSync } = await import('child_process'); const kubeconfig = process.env.KUBECONFIG || './kubeconfig-test.yaml'; try { const output = execSync(`kubectl --kubeconfig=${kubeconfig} ${command}`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }); return output; } catch (error: any) { // Return empty string for errors (e.g., resources not found) // The --ignore-not-found flag handles most of these gracefully return error.stdout || ''; } } /** * Helper to sleep for specified milliseconds */ private sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Sanitize test name for use in Kubernetes resource names */ private sanitizeName(name: string): string { return name .toLowerCase() .replace(/[^a-z0-9-]/g, '-') .replace(/-+/g, '-') .replace(/^-|-$/g, '') .substring(0, 20); // Limit length for namespace constraints } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vfarcic/dot-ai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server