Skip to content

ADR-004: ComfyUI Mock Mode for Development and Testing ​

Date: 2025-11-06 Status: 🤔 Proposed Decision Makers: Engineering Team Approval Required: Before implementation Related ADRs: None


Executive Summary ​

This ADR proposes a mock execution mode for ComfyUI workers that enables rapid development and testing without requiring full ComfyUI installation, GPU hardware, or model downloads. Mock outputs are defined per-component in ECLI and returned by workers as if real generation occurred.

Current Gap:

  • ❌ Long setup time - ComfyUI + custom nodes + models = 8-15 minute cold start
  • ❌ Hardware dependency - Requires GPU or slow CPU mode
  • ❌ No fast iteration - Testing workflow changes requires full execution
  • ❌ Difficult local dev - Developers need full ComfyUI stack locally

Proposed Solution:

  • 🎯 Component-level mock outputs - Each workflow has a mockOutput.json in ECLI
  • 🚀 Worker-side mock detection - COMFYUI_MOCK_MODE=true bypasses real ComfyUI
  • 📦 Azure blob copy - Mock outputs copied to expected job output locations
  • 🔄 Transparent to API - API/frontend unaware, receives normal job results

Impact:

  • Before: 8-15min setup, GPU required, slow iteration
  • After: <30s setup, CPU-only, instant "generation" results

Table of Contents ​

  1. Context
  2. Problem Statement
  3. Decision
  4. Technical Design
  5. Implementation Details
  6. Consequences
  7. Alternatives Considered

Context ​

Current ComfyUI Workflow Execution ​

API receives job → Redis queue → Worker claims job
                                       ↓
                          Worker.processJob(jobData)
                                       ↓
                    ComfyUIRestStreamConnector.processJob()
                                       ↓
                    Submit workflow to ComfyUI via REST API
                                       ↓
                    ComfyUI executes nodes (10s-10min)
                                       ↓
                    ComfyUI saves output to Azure blob
                                       ↓
                    ComfyUI returns output URLs
                                       ↓
                    Worker returns JobResult to API
                                       ↓
                    API returns result to frontend

Current Pain Points ​

Development Friction:

  • Full ComfyUI installation required (~2-5GB download)
  • Custom nodes installation (64+ nodes, ~3-5 minutes)
  • Model downloads (2-15GB per model, 5+ minutes)
  • GPU required for reasonable performance
  • Total setup: 8-15 minutes minimum

Testing Friction:

  • Every workflow change requires full execution
  • Can't test job queue logic without ComfyUI
  • Difficult to reproduce specific outputs
  • Slow feedback loops

Local Development Friction:

  • Developers need full ComfyUI stack
  • Hardware requirements (GPU preferred)
  • Inconsistent environments between devs

Problem Statement ​

How do we enable rapid development and testing of the job queue system, API, and frontend without requiring full ComfyUI execution?

Requirements:

  1. ✅ Fast setup (< 1 minute)
  2. ✅ No GPU required
  3. ✅ Reproducible outputs
  4. ✅ Transparent to API/frontend
  5. ✅ Easy to add new mock outputs
  6. ✅ Per-component/workflow mocking

Decision ​

We will implement component-level mock mode with the following architecture:

1. Mock Output Storage (File System) ​

Decision: Use file-based storage in container (Simplest approach)

Mock outputs stored in container file system:

/workspace/comfyui-mocks/
├── txt2img-flux.png
├── upscale-esrgan.png
├── video-gen.mp4
├── image-merge.png
└── ...

Naming Convention: {workflow_name}.{extension}

Why File System over Redis/Database:

  • ✅ Simplest implementation - no external dependencies
  • ✅ Zero configuration - just mount/copy files into container
  • ✅ No API changes needed - workers have direct file access
  • ✅ Version controlled - mock files in repo alongside code
  • ✅ Fast lookup - direct file path construction
  • ✅ Easy to update - replace files, rebuild container

Storage Location:

  • Development: /workspace/comfyui-mocks/ (local directory)
  • Container: Baked into base machine image or mounted volume

2. Worker Mock Mode Detection ​

Environment Variable:

bash
COMFYUI_MOCK_MODE=true  # Enables mock mode
COMFYUI_MOCK_DIR=/workspace/comfyui-mocks  # Mock files directory

Worker Behavior:

typescript
// In ComfyUIRestStreamConnector.processJob()
if (process.env.COMFYUI_MOCK_MODE === 'true') {
  return this.processMockJob(jobData, progressCallback);
}
// Otherwise: normal ComfyUI execution

3. Mock Job Execution Flow ​

Worker claims job → Detects COMFYUI_MOCK_MODE=true
                              ↓
           Extract workflow_name from jobData.payload
                              ↓
      Construct mock file path: `/workspace/comfyui-mocks/{workflow_name}.png`
                              ↓
           Check if mock file exists
                              ↓
         Read mock file from disk
                              ↓
         Generate target blob path from job context
         (e.g., user-123/job-456/output.png)
                              ↓
      Upload mock file to Azure blob at target location
                              ↓
      Return JobResult with new CDN URL
                              ↓
      API receives result (thinks it was real generation)

4. Azure Blob Upload Mechanism ​

Upload Implementation:

typescript
async uploadMockOutputToJobLocation(
  workflowName: string,
  jobContext: { storage: { cdnUrl, prefix }, jobId }
): Promise<string> {
  // 1. Construct mock file path
  const mockDir = process.env.COMFYUI_MOCK_DIR || '/workspace/comfyui-mocks';
  const mockFilePath = `${mockDir}/${workflowName}.png`;

  // 2. Check if mock file exists
  if (!fs.existsSync(mockFilePath)) {
    throw new Error(`No mock file found for workflow: ${workflowName} at ${mockFilePath}`);
  }

  // 3. Read mock file from disk
  const mockFileBuffer = await fs.promises.readFile(mockFilePath);

  // 4. Generate target path
  const targetPath = `${jobContext.prefix}/${jobContext.jobId}/output.png`;

  // 5. Upload to Azure blob storage
  await azureBlobClient.upload(targetPath, mockFileBuffer);

  // 6. Return new CDN URL
  return `${jobContext.storage.cdnUrl}/${targetPath}`;
}

Technical Design ​

Directory Structure ​

Mock Files Location:

/workspace/comfyui-mocks/
├── txt2img-flux.png          # Text-to-image workflow mock
├── upscale-esrgan.png         # Upscaler workflow mock
├── video-gen.mp4              # Video generation mock
├── image-merge.png            # Image merging mock
├── background-removal.png     # Background removal mock
└── README.md                  # Documentation for adding mocks

File Naming Convention:

  • Format: {workflow_name}.{extension}
  • Workflow name must match exactly (case-sensitive)
  • Extension determines MIME type (.png, .jpg, .mp4, .webp, etc.)

Development Setup:

bash
# Create mock directory
mkdir -p /workspace/comfyui-mocks

# Add sample mock outputs
cp sample-images/flux-output.png /workspace/comfyui-mocks/txt2img-flux.png
cp sample-images/upscaled.png /workspace/comfyui-mocks/upscale-esrgan.png

Container Setup:

dockerfile
# In machine Dockerfile
COPY comfyui-mocks/ /workspace/comfyui-mocks/

Worker Connector Changes ​

New Method: processMockJob()

typescript
// In ComfyUIRestStreamConnector
async processMockJob(jobData: JobData, progressCallback: ProgressCallback): Promise<JobResult> {
  const startTime = Date.now();

  // 1. Extract workflow name
  const workflowName = jobData.payload?.workflow_name || jobData.metadata?.workflow_name;

  if (!workflowName) {
    throw new Error('No workflow_name found in job payload or metadata');
  }

  logger.info(`[MOCK MODE] Processing mock job for workflow: ${workflowName}`);

  // 2. Construct mock file path
  const mockDir = process.env.COMFYUI_MOCK_DIR || '/workspace/comfyui-mocks';
  const mockFilePath = path.join(mockDir, `${workflowName}.png`);

  // 3. Check if mock file exists
  if (!fs.existsSync(mockFilePath)) {
    throw new Error(`No mock file found for workflow: ${workflowName} at ${mockFilePath}`);
  }

  // 4. Simulate progress updates
  await progressCallback({ job_id: jobData.id, progress: 10, message: 'Mock: Initializing' });
  await this.sleep(500);
  await progressCallback({ job_id: jobData.id, progress: 50, message: 'Mock: Processing' });
  await this.sleep(500);

  // 5. Upload mock file to job-specific Azure blob location
  const outputUrl = await this.uploadMockOutputToJobLocation(
    workflowName,
    jobData.ctx // Contains storage config + prefix
  );

  await progressCallback({ job_id: jobData.id, progress: 100, message: 'Mock: Complete' });

  logger.info(`[MOCK MODE] Completed mock job for ${workflowName}: ${outputUrl}`);

  // 6. Return result identical to real ComfyUI output
  return {
    success: true,
    data: {
      image_url: outputUrl,
      mime_type: 'image/png',
      content_type: 'image',
      mock_mode: true // For debugging
    },
    processing_time_ms: Date.now() - startTime,
    service_metadata: {
      service_version: this.version,
      service_type: this.service_type,
      mock_execution: true
    }
  };
}

Implementation Details ​

Phase 1: Core Mock Mode (MVP) ​

Setup:

  1. ✅ Create /workspace/comfyui-mocks/ directory structure
  2. ✅ Add sample mock files for 2-3 core workflows
  3. ✅ Add mock directory to .gitignore (or version control sample mocks)

Worker Changes:

  1. ✅ Add COMFYUI_MOCK_MODE environment variable support
  2. ✅ Add COMFYUI_MOCK_DIR environment variable (defaults to /workspace/comfyui-mocks)
  3. ✅ Implement processMockJob() in ComfyUIWebSocketConnector
  4. ✅ Implement uploadMockOutputToJobLocation() helper
  5. ✅ Add file existence validation with clear error messages

Container Changes:

  1. ✅ Copy mock files into container image (optional, for baked mocks)
  2. ✅ Or mount mock directory as volume (for local development)

Testing:

  1. ✅ Unit tests for processMockJob()
  2. ✅ Unit tests for mock file discovery and validation
  3. ✅ Integration test: Submit job → Worker returns mock → Verify blob upload
  4. ✅ E2E test: API → Queue → Mock Worker → Result

Phase 2: Enhanced Mocking (Future) ​

File Extensions and MIME Type Detection:

bash
/workspace/comfyui-mocks/
├── txt2img-flux.png      # Auto-detect: image/png
├── video-gen.mp4         # Auto-detect: video/mp4
├── audio-gen.wav         # Auto-detect: audio/wav

Multiple Mock Variants:

bash
/workspace/comfyui-mocks/
├── txt2img-flux.default.png
├── txt2img-flux.high-quality.png
├── txt2img-flux.error.png

Mock Selection:

bash
COMFYUI_MOCK_VARIANT=high-quality  # Which mock variant to use

Metadata Files (Optional):

json
// /workspace/comfyui-mocks/txt2img-flux.meta.json
{
  "width": 512,
  "height": 512,
  "description": "Sample flux output",
  "variants": ["default", "high-quality", "error"]
}

Consequences ​

Positive ​

✅ Rapid Local Development

  • No ComfyUI installation needed
  • CPU-only development machines work
  • Instant "generation" results

✅ Faster Testing

  • Test job queue logic without GPU
  • Reproducible outputs for E2E tests
  • Faster CI/CD pipelines

✅ Cost Reduction

  • No GPU instances needed for development
  • Fewer model downloads (bandwidth savings)
  • Faster developer onboarding

✅ Transparent to API

  • No API changes required
  • Frontend receives normal responses
  • Easy to toggle mock mode on/off

Negative ​

❌ Maintenance Overhead

  • Need to update mock files when workflows change output format
  • Mock files need to be kept in sync with workflow expectations

❌ Not True E2E

  • Doesn't test actual ComfyUI execution
  • Won't catch ComfyUI-specific bugs
  • Need real execution tests for production validation

❌ Storage Requirements

  • Mock files consume disk space (typically 0.5-5MB per workflow)
  • Need to manage mock file versions

Mitigation Strategies ​

Mock Output Staleness:

  • Automated tests to verify mock outputs match current workflow output formats
  • Version mock files alongside workflow versions
  • Document mock file update process in README

Storage Management:

  • Compress mock files where appropriate
  • Use symbolic links for duplicate mocks
  • Consider on-demand download for large mocks (Phase 2)

Production Safety:

  • COMFYUI_MOCK_MODE defaults to false
  • Production environments never set mock mode
  • CI includes both mock and real execution tests
  • Clear [MOCK MODE] prefix in all log messages

Alternatives Considered ​

Alternative 1: Simulation Connector (Rejected) ​

Approach: Use existing SimulationConnector for all services

Rejected Because:

  • ❌ SimulationConnector is service-agnostic (no workflow awareness)
  • ❌ Doesn't understand component-level mocking
  • ❌ Doesn't copy blobs to expected locations
  • ❌ Not ComfyUI-specific

Alternative 2: Mock ComfyUI Server (Rejected) ​

Approach: Create fake ComfyUI HTTP/WebSocket server that returns mocks

Rejected Because:

  • ❌ More complex implementation (full server mock)
  • ❌ Slower (still goes through WebSocket protocol)
  • ❌ Doesn't gain us anything over direct mocking
  • ❌ Harder to maintain

Alternative 3: Redis-Based Mock Storage (Rejected) ​

Approach: Store mock output metadata in Redis, pointing to CDN URLs

Rejected Because:

  • ❌ Requires ECLI to publish mock data on startup
  • ❌ Adds external dependency (though Redis already used)
  • ❌ More complex than file system
  • ❌ Mock data lost if Redis flushes
  • ❌ Harder to version control mock outputs

Alternative 4: Database Mock Storage (Rejected) ​

Approach: Store mock outputs in database with workflow schema

Rejected Because:

  • ❌ Workers don't currently have DB access
  • ❌ Requires schema migrations
  • ❌ Slower implementation
  • ❌ More complex than file system
  • ❌ Overkill for simple mock data

Open Questions ​

  1. Q: Should mock mode also skip model downloads? A: Yes - mock mode should bypass all ComfyUI installation (covered by GPU_MODE=mock + COMPONENTS="")

  2. Q: How do we handle workflows without mock outputs defined? A: Throw clear error: "No mock output defined for workflow: {name}"

  3. Q: Should API know about mock mode? A: No - API is agnostic, workers handle mocking transparently

  4. Q: What about collection-based components? A: Collections resolve to individual workflows, each has its own mock output


Decision ​

Status: 🤔 Proposed

We will implement Phase 1: Core Mock Mode as described above:

  • Mock files stored in /workspace/comfyui-mocks/ directory
  • File naming convention: {workflow_name}.{extension}
  • Workers detect COMFYUI_MOCK_MODE=true and bypass ComfyUI
  • Mock files uploaded to Azure blob with job-specific paths
  • API/frontend receive identical responses as real execution

Implementation Priority: HIGH (enables fast local development)

Estimated Effort:

  • Mock directory setup: 1 hour
  • Worker changes: 4 hours
  • Testing: 3 hours
  • Total: ~1 day (simplified from original 2-day estimate)

References ​


Approval Required From:

  • [ ] Tech Lead
  • [ ] DevOps (for environment variable rollout)
  • [ ] Frontend (for awareness of mock mode in testing)

Released under the MIT License.