UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

425 lines (345 loc) • 16.1 kB
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { type Architecture, ImageResizer, imageResizer, } from '@/server/services/comfyui/utils/imageResizer'; // Mock debug module vi.mock('debug', () => ({ default: vi.fn(() => vi.fn()), })); describe('imageResizer.ts', () => { let resizer: ImageResizer; beforeEach(() => { vi.clearAllMocks(); resizer = new ImageResizer(); }); describe('ImageResizer class', () => { describe('calculateTargetDimensions', () => { describe('FLUX architecture', () => { it('should not resize when dimensions are within limits', () => { const result = resizer.calculateTargetDimensions(1024, 768, 'FLUX'); expect(result).toEqual({ width: 1024, height: 768, needsResize: false, }); }); it('should resize when width exceeds maximum', () => { const result = resizer.calculateTargetDimensions(2000, 800, 'FLUX'); expect(result.needsResize).toBe(true); expect(result.width).toBeLessThanOrEqual(1440); expect(result.height).toBeLessThanOrEqual(1440); expect(result.width % 32).toBe(0); // Should be rounded to step expect(result.height % 32).toBe(0); }); it('should resize when height exceeds maximum', () => { const result = resizer.calculateTargetDimensions(800, 2000, 'FLUX'); expect(result.needsResize).toBe(true); expect(result.width).toBeLessThanOrEqual(1440); expect(result.height).toBeLessThanOrEqual(1440); expect(result.width % 32).toBe(0); expect(result.height % 32).toBe(0); }); it('should resize when dimensions are too small', () => { const result = resizer.calculateTargetDimensions(100, 200, 'FLUX'); expect(result.needsResize).toBe(true); expect(result.width).toBeGreaterThanOrEqual(256); expect(result.height).toBeGreaterThanOrEqual(256); expect(result.width % 32).toBe(0); expect(result.height % 32).toBe(0); }); it('should handle exact limit dimensions', () => { const result = resizer.calculateTargetDimensions(1440, 256, 'FLUX'); expect(result).toEqual({ width: 1440, height: 256, needsResize: false, }); }); it('should round to step size (32) correctly', () => { const result = resizer.calculateTargetDimensions(1000, 600, 'FLUX'); if (result.needsResize) { expect(result.width % 32).toBe(0); expect(result.height % 32).toBe(0); } else { // Original dimensions should be accepted expect(result.width).toBe(1000); expect(result.height).toBe(600); } }); it('should handle aspect ratio constraints', () => { // Test extreme aspect ratios const wideResult = resizer.calculateTargetDimensions(2100, 900, 'FLUX'); // 2.33 ratio const tallResult = resizer.calculateTargetDimensions(900, 2100, 'FLUX'); // 0.43 ratio expect(wideResult.needsResize).toBe(true); expect(tallResult.needsResize).toBe(true); // Should still produce valid dimensions within model limits expect(wideResult.width).toBeLessThanOrEqual(1440); expect(wideResult.height).toBeLessThanOrEqual(1440); expect(tallResult.width).toBeLessThanOrEqual(1440); expect(tallResult.height).toBeLessThanOrEqual(1440); }); }); describe('SD1 architecture', () => { it('should not resize for optimal dimensions', () => { const result = resizer.calculateTargetDimensions(512, 512, 'SD1'); expect(result).toEqual({ width: 512, height: 512, needsResize: false, }); }); it('should resize when exceeding maximum', () => { const result = resizer.calculateTargetDimensions(1000, 800, 'SD1'); expect(result.needsResize).toBe(true); expect(result.width).toBeLessThanOrEqual(768); expect(result.height).toBeLessThanOrEqual(768); }); it('should resize when below minimum', () => { const result = resizer.calculateTargetDimensions(100, 200, 'SD1'); expect(result.needsResize).toBe(true); expect(result.width).toBeGreaterThanOrEqual(256); expect(result.height).toBeGreaterThanOrEqual(256); }); it('should handle edge case at exact limits', () => { const maxResult = resizer.calculateTargetDimensions(768, 768, 'SD1'); const minResult = resizer.calculateTargetDimensions(256, 256, 'SD1'); expect(maxResult.needsResize).toBe(false); expect(minResult.needsResize).toBe(false); }); }); describe('SD3 architecture', () => { it('should not resize for optimal dimensions', () => { const result = resizer.calculateTargetDimensions(1024, 1024, 'SD3'); expect(result).toEqual({ width: 1024, height: 1024, needsResize: false, }); }); it('should resize when exceeding maximum', () => { const result = resizer.calculateTargetDimensions(3000, 2000, 'SD3'); expect(result.needsResize).toBe(true); expect(result.width).toBeLessThanOrEqual(2048); expect(result.height).toBeLessThanOrEqual(2048); }); it('should resize when below minimum', () => { const result = resizer.calculateTargetDimensions(300, 400, 'SD3'); expect(result.needsResize).toBe(true); expect(result.width).toBeGreaterThanOrEqual(512); expect(result.height).toBeGreaterThanOrEqual(512); }); }); describe('SDXL architecture', () => { it('should not resize for optimal dimensions', () => { const result = resizer.calculateTargetDimensions(1024, 1024, 'SDXL'); expect(result).toEqual({ width: 1024, height: 1024, needsResize: false, }); }); it('should resize when exceeding maximum', () => { const result = resizer.calculateTargetDimensions(2500, 2500, 'SDXL'); expect(result.needsResize).toBe(true); expect(result.width).toBeLessThanOrEqual(2048); expect(result.height).toBeLessThanOrEqual(2048); }); it('should resize when below minimum', () => { const result = resizer.calculateTargetDimensions(400, 300, 'SDXL'); expect(result.needsResize).toBe(true); expect(result.width).toBeGreaterThanOrEqual(512); expect(result.height).toBeGreaterThanOrEqual(512); }); }); describe('edge cases and error handling', () => { it('should handle zero dimensions', () => { expect(() => resizer.calculateTargetDimensions(0, 100, 'FLUX')).not.toThrow(); expect(() => resizer.calculateTargetDimensions(100, 0, 'FLUX')).not.toThrow(); expect(() => resizer.calculateTargetDimensions(0, 0, 'FLUX')).not.toThrow(); }); it('should handle negative dimensions', () => { expect(() => resizer.calculateTargetDimensions(-100, 100, 'FLUX')).not.toThrow(); expect(() => resizer.calculateTargetDimensions(100, -100, 'FLUX')).not.toThrow(); }); it('should handle very large dimensions', () => { const result = resizer.calculateTargetDimensions(10000, 10000, 'FLUX'); expect(result.needsResize).toBe(true); expect(result.width).toBeLessThanOrEqual(1440); expect(result.height).toBeLessThanOrEqual(1440); }); it('should handle very small dimensions', () => { const result = resizer.calculateTargetDimensions(1, 1, 'FLUX'); expect(result.needsResize).toBe(true); expect(result.width).toBeGreaterThanOrEqual(256); expect(result.height).toBeGreaterThanOrEqual(256); }); it('should handle unknown architecture gracefully', () => { expect(() => { resizer.calculateTargetDimensions(1024, 768, 'UNKNOWN' as Architecture); }).toThrow(); }); it('should handle floating point dimensions', () => { const result = resizer.calculateTargetDimensions(1024.5, 768.7, 'FLUX'); // Results should be numbers (may be integers or floats depending on calculation) expect(typeof result.width).toBe('number'); expect(typeof result.height).toBe('number'); expect(Number.isFinite(result.width)).toBe(true); expect(Number.isFinite(result.height)).toBe(true); }); it('should maintain aspect ratio during scaling', () => { const originalAspectRatio = 16 / 9; const originalWidth = 1920; const originalHeight = 1080; const result = resizer.calculateTargetDimensions(originalWidth, originalHeight, 'FLUX'); if (result.needsResize) { const newAspectRatio = result.width / result.height; expect(Math.abs(newAspectRatio - originalAspectRatio)).toBeLessThan(0.1); } }); }); describe('performance and consistency', () => { it('should be deterministic for same inputs', () => { const inputs = [ [1024, 768, 'FLUX'], [512, 512, 'SD1'], [2048, 1024, 'SDXL'], ] as const; inputs.forEach(([width, height, arch]) => { const result1 = resizer.calculateTargetDimensions(width, height, arch); const result2 = resizer.calculateTargetDimensions(width, height, arch); expect(result1).toEqual(result2); }); }); it('should handle rapid successive calls', () => { const results = []; for (let i = 0; i < 1000; i++) { const result = resizer.calculateTargetDimensions(1024 + i, 768 + i, 'FLUX'); results.push(result); } expect(results).toHaveLength(1000); results.forEach((result) => { expect(typeof result.width).toBe('number'); expect(typeof result.height).toBe('number'); expect(typeof result.needsResize).toBe('boolean'); }); }); }); }); }); describe('private method testing through public interface', () => { describe('calculateRatio method (through public interface)', () => { it('should calculate ratios correctly in various scenarios', () => { // Test through calculateTargetDimensions which uses calculateRatioFromDimensions const wideResult = resizer.calculateTargetDimensions(1600, 900, 'FLUX'); // 16:9 const squareResult = resizer.calculateTargetDimensions(1000, 1000, 'FLUX'); // 1:1 const tallResult = resizer.calculateTargetDimensions(900, 1600, 'FLUX'); // 9:16 expect(typeof wideResult).toBe('object'); expect(typeof squareResult).toBe('object'); expect(typeof tallResult).toBe('object'); }); }); describe('isWithinRatioRange method (through public interface)', () => { it('should handle ratio range validation through FLUX constraints', () => { // Test extreme ratios that should trigger ratio range checks const extremeWide = resizer.calculateTargetDimensions(3000, 1000, 'FLUX'); // 3:1 (exceeds 21:9) const extremeTall = resizer.calculateTargetDimensions(1000, 3000, 'FLUX'); // 1:3 (exceeds 9:21) expect(extremeWide.needsResize).toBe(true); expect(extremeTall.needsResize).toBe(true); }); }); describe('getModelLimits method (through public interface)', () => { it('should use correct limits for each architecture', () => { // Test by checking if results respect known limits const fluxResult = resizer.calculateTargetDimensions(2000, 2000, 'FLUX'); const sd1Result = resizer.calculateTargetDimensions(1000, 1000, 'SD1'); const sd3Result = resizer.calculateTargetDimensions(3000, 3000, 'SD3'); const sdxlResult = resizer.calculateTargetDimensions(3000, 3000, 'SDXL'); expect(fluxResult.width).toBeLessThanOrEqual(1440); // FLUX max expect(sd1Result.width).toBeLessThanOrEqual(768); // SD1 max expect(sd3Result.width).toBeLessThanOrEqual(2048); // SD3 max expect(sdxlResult.width).toBeLessThanOrEqual(2048); // SDXL max }); }); }); describe('singleton instance', () => { it('should export a singleton instance', () => { expect(imageResizer).toBeInstanceOf(ImageResizer); }); it('should maintain state across calls', () => { const result1 = imageResizer.calculateTargetDimensions(1024, 768, 'FLUX'); const result2 = imageResizer.calculateTargetDimensions(1024, 768, 'FLUX'); expect(result1).toEqual(result2); }); it('should be the same instance when imported multiple times', () => { // This tests that the singleton pattern is working correctly expect(imageResizer).toBeDefined(); expect(typeof imageResizer.calculateTargetDimensions).toBe('function'); }); }); describe('integration scenarios', () => { it('should handle common use cases correctly', () => { const commonSizes = [ { width: 512, height: 512, arch: 'SD1' as Architecture }, { width: 1024, height: 1024, arch: 'SDXL' as Architecture }, { width: 1440, height: 1024, arch: 'FLUX' as Architecture }, { width: 768, height: 512, arch: 'SD1' as Architecture }, ]; commonSizes.forEach(({ width, height, arch }) => { const result = resizer.calculateTargetDimensions(width, height, arch); expect(result).toHaveProperty('width'); expect(result).toHaveProperty('height'); expect(result).toHaveProperty('needsResize'); expect(typeof result.needsResize).toBe('boolean'); }); }); it('should handle img2img workflow dimensions', () => { // Common img2img input sizes const img2imgSizes = [ [1920, 1080], // Full HD [1280, 720], // HD [800, 600], // SVGA [640, 480], // VGA ]; img2imgSizes.forEach(([width, height]) => { ['FLUX', 'SDXL', 'SD3', 'SD1'].forEach((arch) => { const result = resizer.calculateTargetDimensions(width, height, arch as Architecture); // All results should be valid expect(result.width).toBeGreaterThan(0); expect(result.height).toBeGreaterThan(0); expect(Number.isInteger(result.width)).toBe(true); expect(Number.isInteger(result.height)).toBe(true); }); }); }); }); describe('boundary testing', () => { it('should handle boundary conditions for each architecture', () => { const architectures: Architecture[] = ['FLUX', 'SD1', 'SD3', 'SDXL']; architectures.forEach((arch) => { // Test minimum boundary const minResult = resizer.calculateTargetDimensions(1, 1, arch); expect(minResult.needsResize).toBe(true); // Test maximum boundary const maxResult = resizer.calculateTargetDimensions(10000, 10000, arch); expect(maxResult.needsResize).toBe(true); }); }); it('should handle step size boundaries for FLUX', () => { // Test dimensions that are close to but not exactly divisible by 32 const testCases = [ [1023, 767], // Just below step boundary [1025, 769], // Just above step boundary [1056, 800], // Exactly on step boundary ]; testCases.forEach(([width, height]) => { const result = resizer.calculateTargetDimensions(width, height, 'FLUX'); if (result.needsResize) { expect(result.width % 32).toBe(0); expect(result.height % 32).toBe(0); } }); }); }); });