mirror of
				https://github.com/langgenius/dify.git
				synced 2025-11-03 20:33:00 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			296 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			296 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {
 | 
						|
  asyncRunSafe,
 | 
						|
  canFindTool,
 | 
						|
  correctModelProvider,
 | 
						|
  correctToolProvider,
 | 
						|
  fetchWithRetry,
 | 
						|
  getPurifyHref,
 | 
						|
  getTextWidthWithCanvas,
 | 
						|
  randomString,
 | 
						|
  removeSpecificQueryParam,
 | 
						|
  sleep,
 | 
						|
} from './index'
 | 
						|
 | 
						|
describe('sleep', () => {
 | 
						|
  it('should wait for the specified time', async () => {
 | 
						|
    const timeVariance = 10
 | 
						|
    const sleepTime = 100
 | 
						|
    const start = Date.now()
 | 
						|
    await sleep(sleepTime)
 | 
						|
    const elapsed = Date.now() - start
 | 
						|
    expect(elapsed).toBeGreaterThanOrEqual(sleepTime - timeVariance)
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
describe('asyncRunSafe', () => {
 | 
						|
  it('should return [null, result] when promise resolves', async () => {
 | 
						|
    const result = await asyncRunSafe(Promise.resolve('success'))
 | 
						|
    expect(result).toEqual([null, 'success'])
 | 
						|
  })
 | 
						|
 | 
						|
  it('should return [error] when promise rejects', async () => {
 | 
						|
    const error = new Error('test error')
 | 
						|
    const result = await asyncRunSafe(Promise.reject(error))
 | 
						|
    expect(result).toEqual([error])
 | 
						|
  })
 | 
						|
 | 
						|
  it('should return [Error] when promise rejects with undefined', async () => {
 | 
						|
    // eslint-disable-next-line prefer-promise-reject-errors
 | 
						|
    const result = await asyncRunSafe(Promise.reject())
 | 
						|
    expect(result[0]).toBeInstanceOf(Error)
 | 
						|
    expect(result[0]?.message).toBe('unknown error')
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
describe('getTextWidthWithCanvas', () => {
 | 
						|
  let originalCreateElement: typeof document.createElement
 | 
						|
 | 
						|
  beforeEach(() => {
 | 
						|
    // Store original implementation
 | 
						|
    originalCreateElement = document.createElement
 | 
						|
 | 
						|
    // Mock canvas and context
 | 
						|
    const measureTextMock = jest.fn().mockReturnValue({ width: 100 })
 | 
						|
    const getContextMock = jest.fn().mockReturnValue({
 | 
						|
      measureText: measureTextMock,
 | 
						|
      font: '',
 | 
						|
    })
 | 
						|
 | 
						|
    document.createElement = jest.fn().mockReturnValue({
 | 
						|
      getContext: getContextMock,
 | 
						|
    })
 | 
						|
  })
 | 
						|
 | 
						|
  afterEach(() => {
 | 
						|
    // Restore original implementation
 | 
						|
    document.createElement = originalCreateElement
 | 
						|
  })
 | 
						|
 | 
						|
  it('should return the width of text', () => {
 | 
						|
    const width = getTextWidthWithCanvas('test text')
 | 
						|
    expect(width).toBe(100)
 | 
						|
  })
 | 
						|
 | 
						|
  it('should return 0 if context is not available', () => {
 | 
						|
    // Override mock for this test
 | 
						|
    document.createElement = jest.fn().mockReturnValue({
 | 
						|
      getContext: () => null,
 | 
						|
    })
 | 
						|
 | 
						|
    const width = getTextWidthWithCanvas('test text')
 | 
						|
    expect(width).toBe(0)
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
describe('randomString', () => {
 | 
						|
  it('should generate string of specified length', () => {
 | 
						|
    const result = randomString(10)
 | 
						|
    expect(result.length).toBe(10)
 | 
						|
  })
 | 
						|
 | 
						|
  it('should only contain valid characters', () => {
 | 
						|
    const result = randomString(100)
 | 
						|
    const validChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
 | 
						|
    for (const char of result)
 | 
						|
      expect(validChars).toContain(char)
 | 
						|
  })
 | 
						|
 | 
						|
  it('should generate different strings on consecutive calls', () => {
 | 
						|
    const result1 = randomString(20)
 | 
						|
    const result2 = randomString(20)
 | 
						|
    expect(result1).not.toEqual(result2)
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
describe('getPurifyHref', () => {
 | 
						|
  it('should return empty string for falsy input', () => {
 | 
						|
    expect(getPurifyHref('')).toBe('')
 | 
						|
    expect(getPurifyHref(undefined as any)).toBe('')
 | 
						|
  })
 | 
						|
 | 
						|
  it('should escape HTML characters', () => {
 | 
						|
    expect(getPurifyHref('<script>alert("xss")</script>')).not.toContain('<script>')
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
describe('fetchWithRetry', () => {
 | 
						|
  it('should return successfully on first try', async () => {
 | 
						|
    const successData = { status: 'success' }
 | 
						|
    const promise = Promise.resolve(successData)
 | 
						|
 | 
						|
    const result = await fetchWithRetry(promise)
 | 
						|
 | 
						|
    expect(result).toEqual([null, successData])
 | 
						|
  })
 | 
						|
 | 
						|
  // it('should retry and succeed on second attempt', async () => {
 | 
						|
  //   let attemptCount = 0
 | 
						|
  //   const mockFn = new Promise((resolve, reject) => {
 | 
						|
  //     attemptCount++
 | 
						|
  //     if (attemptCount === 1)
 | 
						|
  //       reject(new Error('First attempt failed'))
 | 
						|
  //     else
 | 
						|
  //       resolve('success')
 | 
						|
  //   })
 | 
						|
 | 
						|
  //   const result = await fetchWithRetry(mockFn)
 | 
						|
 | 
						|
  //   expect(result).toEqual([null, 'success'])
 | 
						|
  //   expect(attemptCount).toBe(2)
 | 
						|
  // })
 | 
						|
 | 
						|
  // it('should stop after max retries and return last error', async () => {
 | 
						|
  //   const testError = new Error('Test error')
 | 
						|
  //   const promise = Promise.reject(testError)
 | 
						|
 | 
						|
  //   const result = await fetchWithRetry(promise, 2)
 | 
						|
 | 
						|
  //   expect(result).toEqual([testError])
 | 
						|
  // })
 | 
						|
 | 
						|
  // it('should handle non-Error rejection with custom error', async () => {
 | 
						|
  //   const stringError = 'string error message'
 | 
						|
  //   const promise = Promise.reject(stringError)
 | 
						|
 | 
						|
  //   const result = await fetchWithRetry(promise, 0)
 | 
						|
 | 
						|
  //   expect(result[0]).toBeInstanceOf(Error)
 | 
						|
  //   expect(result[0]?.message).toBe('unknown error')
 | 
						|
  // })
 | 
						|
 | 
						|
  // it('should use default 3 retries when retries parameter is not provided', async () => {
 | 
						|
  //   let attempts = 0
 | 
						|
  //   const mockFn = () => new Promise((resolve, reject) => {
 | 
						|
  //     attempts++
 | 
						|
  //     reject(new Error(`Attempt ${attempts} failed`))
 | 
						|
  //   })
 | 
						|
 | 
						|
  //   await fetchWithRetry(mockFn())
 | 
						|
 | 
						|
  //   expect(attempts).toBe(4) // Initial attempt + 3 retries
 | 
						|
  // })
 | 
						|
})
 | 
						|
 | 
						|
describe('correctModelProvider', () => {
 | 
						|
  it('should return empty string for falsy input', () => {
 | 
						|
    expect(correctModelProvider('')).toBe('')
 | 
						|
  })
 | 
						|
 | 
						|
  it('should return the provider if it already contains a slash', () => {
 | 
						|
    expect(correctModelProvider('company/model')).toBe('company/model')
 | 
						|
  })
 | 
						|
 | 
						|
  it('should format google provider correctly', () => {
 | 
						|
    expect(correctModelProvider('google')).toBe('langgenius/gemini/google')
 | 
						|
  })
 | 
						|
 | 
						|
  it('should format standard providers correctly', () => {
 | 
						|
    expect(correctModelProvider('openai')).toBe('langgenius/openai/openai')
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
describe('correctToolProvider', () => {
 | 
						|
  it('should return empty string for falsy input', () => {
 | 
						|
    expect(correctToolProvider('')).toBe('')
 | 
						|
  })
 | 
						|
 | 
						|
  it('should return the provider if toolInCollectionList is true', () => {
 | 
						|
    expect(correctToolProvider('any-provider', true)).toBe('any-provider')
 | 
						|
  })
 | 
						|
 | 
						|
  it('should return the provider if it already contains a slash', () => {
 | 
						|
    expect(correctToolProvider('company/tool')).toBe('company/tool')
 | 
						|
  })
 | 
						|
 | 
						|
  it('should format special tool providers correctly', () => {
 | 
						|
    expect(correctToolProvider('stepfun')).toBe('langgenius/stepfun_tool/stepfun')
 | 
						|
    expect(correctToolProvider('jina')).toBe('langgenius/jina_tool/jina')
 | 
						|
  })
 | 
						|
 | 
						|
  it('should format standard tool providers correctly', () => {
 | 
						|
    expect(correctToolProvider('standard')).toBe('langgenius/standard/standard')
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
describe('canFindTool', () => {
 | 
						|
  it('should match when IDs are identical', () => {
 | 
						|
    expect(canFindTool('tool-id', 'tool-id')).toBe(true)
 | 
						|
  })
 | 
						|
 | 
						|
  it('should match when provider ID is formatted with standard pattern', () => {
 | 
						|
    expect(canFindTool('langgenius/tool-id/tool-id', 'tool-id')).toBe(true)
 | 
						|
  })
 | 
						|
 | 
						|
  it('should match when provider ID is formatted with tool pattern', () => {
 | 
						|
    expect(canFindTool('langgenius/tool-id_tool/tool-id', 'tool-id')).toBe(true)
 | 
						|
  })
 | 
						|
 | 
						|
  it('should not match when IDs are completely different', () => {
 | 
						|
    expect(canFindTool('provider-a', 'tool-b')).toBe(false)
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
describe('removeSpecificQueryParam', () => {
 | 
						|
  let originalLocation: Location
 | 
						|
  let originalReplaceState: typeof window.history.replaceState
 | 
						|
 | 
						|
  beforeEach(() => {
 | 
						|
    originalLocation = window.location
 | 
						|
    originalReplaceState = window.history.replaceState
 | 
						|
 | 
						|
    const mockUrl = new URL('https://example.com?param1=value1¶m2=value2¶m3=value3')
 | 
						|
 | 
						|
    // Mock window.location using defineProperty to handle URL properly
 | 
						|
    delete (window as any).location
 | 
						|
    Object.defineProperty(window, 'location', {
 | 
						|
      writable: true,
 | 
						|
      value: {
 | 
						|
        ...originalLocation,
 | 
						|
        href: mockUrl.href,
 | 
						|
        search: mockUrl.search,
 | 
						|
        toString: () => mockUrl.toString(),
 | 
						|
      },
 | 
						|
    })
 | 
						|
 | 
						|
    window.history.replaceState = jest.fn()
 | 
						|
  })
 | 
						|
 | 
						|
  afterEach(() => {
 | 
						|
    Object.defineProperty(window, 'location', {
 | 
						|
      writable: true,
 | 
						|
      value: originalLocation,
 | 
						|
    })
 | 
						|
    window.history.replaceState = originalReplaceState
 | 
						|
  })
 | 
						|
 | 
						|
  it('should remove a single query parameter', () => {
 | 
						|
    removeSpecificQueryParam('param2')
 | 
						|
    expect(window.history.replaceState).toHaveBeenCalledTimes(1)
 | 
						|
    const replaceStateCall = (window.history.replaceState as jest.Mock).mock.calls[0]
 | 
						|
    expect(replaceStateCall[0]).toBe(null)
 | 
						|
    expect(replaceStateCall[1]).toBe('')
 | 
						|
    expect(replaceStateCall[2]).toMatch(/param1=value1/)
 | 
						|
    expect(replaceStateCall[2]).toMatch(/param3=value3/)
 | 
						|
    expect(replaceStateCall[2]).not.toMatch(/param2=value2/)
 | 
						|
  })
 | 
						|
 | 
						|
  it('should remove multiple query parameters', () => {
 | 
						|
    removeSpecificQueryParam(['param1', 'param3'])
 | 
						|
    expect(window.history.replaceState).toHaveBeenCalledTimes(1)
 | 
						|
    const replaceStateCall = (window.history.replaceState as jest.Mock).mock.calls[0]
 | 
						|
    expect(replaceStateCall[2]).toMatch(/param2=value2/)
 | 
						|
    expect(replaceStateCall[2]).not.toMatch(/param1=value1/)
 | 
						|
    expect(replaceStateCall[2]).not.toMatch(/param3=value3/)
 | 
						|
  })
 | 
						|
 | 
						|
  it('should handle non-existent parameters gracefully', () => {
 | 
						|
    removeSpecificQueryParam('nonexistent')
 | 
						|
 | 
						|
    expect(window.history.replaceState).toHaveBeenCalledTimes(1)
 | 
						|
    const replaceStateCall = (window.history.replaceState as jest.Mock).mock.calls[0]
 | 
						|
    expect(replaceStateCall[2]).toMatch(/param1=value1/)
 | 
						|
    expect(replaceStateCall[2]).toMatch(/param2=value2/)
 | 
						|
    expect(replaceStateCall[2]).toMatch(/param3=value3/)
 | 
						|
  })
 | 
						|
})
 |