Ctrl K
ComunidadGuías…

tests/bundle-ai-bump-level.unit.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type { Context } from 'hono'
import type { MiddlewareKeyVariables } from '../supabase/functions/_backend/utils/hono.ts'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { HTTPException } from 'hono/http-exception'

const checkPermissionMock = vi.fn()

vi.mock('../supabase/functions/_backend/utils/rbac.ts', () => ({
  checkPermission: (...args: unknown[]) => checkPermissionMock(...args),
}))

vi.mock('../supabase/functions/_backend/utils/utils.ts', async () => {
  const actual = await vi.importActual<typeof import('../supabase/functions/_backend/utils/utils.ts')>('../supabase/functions/_backend/utils/utils.ts')
  return {
    ...actual,
    getEnv: (_c: unknown, key: string) => {
      if (key === 'BUMP_LEVEL_MODEL')
        return ''
      return ''
    },
    isValidAppId: (appId: string) => typeof appId === 'string' && appId.includes('.'),
  }
})

const { aiBumpLevel } = await import('../supabase/functions/_backend/public/bundle/ai_bump_level.ts')

const apikey = {
  key: 'test-apikey',
  user_id: 'user-1',
} as any

function createContext(ai?: { run: ReturnType<typeof vi.fn> }) {
  return {
    env: { AI: ai },
    get: vi.fn((key: string) => key === 'requestId' ? 'req-1' : undefined),
    json: (body: unknown, status = 200) => Response.json(body, { status }),
  } as unknown as Context<MiddlewareKeyVariables>
}

const validBody = {
  appId: 'com.example.app',
  baseVersion: '1.2.3',
  manifestDiff: {
    added: ['new.js'],
    removed: [],
    changed: ['index.js'],
    counts: { added: 1, removed: 0, changed: 1 },
  },
}

describe('bundle ai_bump_level', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    checkPermissionMock.mockResolvedValue(true)
  })

  it('rejects when upload permission is denied', async () => {
    checkPermissionMock.mockResolvedValue(false)
    const error = await aiBumpLevel(createContext({ run: vi.fn() }), validBody, apikey).catch(caught => caught)
    expect(error).toBeInstanceOf(HTTPException)
    expect(error.status).toBe(400)
    expect(error.message).toContain('You can\'t access this app')
    expect(checkPermissionMock).toHaveBeenCalledWith(expect.anything(), 'app.upload_bundle', { appId: 'com.example.app' })
  })

  it('returns 503 when Workers AI binding is missing', async () => {
    const error = await aiBumpLevel(createContext(undefined), validBody, apikey).catch(caught => caught)
    expect(error).toBeInstanceOf(HTTPException)
    expect(error.status).toBe(503)
    expect(error.cause.error).toBe('ai_unavailable')
  })

  it('rejects invalid body without appId', async () => {
    const error = await aiBumpLevel(createContext({ run: vi.fn() }), {
      baseVersion: '1.0.0',
      manifestDiff: { added: [], removed: [], changed: [], counts: { added: 0, removed: 0, changed: 0 } },
    }, apikey).catch(caught => caught)
    expect(error).toBeInstanceOf(HTTPException)
    expect(error.status).toBe(400)
    expect(error.cause.error).toBe('missing_app_id')
  })

  it('returns level and reason on happy path', async () => {
    const run = vi.fn().mockResolvedValue(JSON.stringify({
      level: 'minor',
      reason: 'Added feature files',
    }))
    const response = await aiBumpLevel(createContext({ run }), validBody, apikey)
    expect(response.status).toBe(200)
    expect(await response.json()).toEqual({
      level: 'minor',
      reason: 'Added feature files',
    })
    expect(run).toHaveBeenCalledTimes(1)
    expect(run.mock.calls[0]![0]).toBe('@cf/qwen/qwen3.8-27b')
    // Qwen 3.8 is a hybrid reasoning model: thinking must be off for this short JSON answer.
    expect(run.mock.calls[0]![1]).toMatchObject({ chat_template_kwargs: { enable_thinking: false }, temperature: 0 })
  })

  it('returns 502 when AI returns an invalid level', async () => {
    const run = vi.fn().mockResolvedValue(JSON.stringify({
      level: 'mega',
      reason: 'bad',
    }))
    const error = await aiBumpLevel(createContext({ run }), validBody, apikey).catch(caught => caught)
    expect(error).toBeInstanceOf(HTTPException)
    expect(error.status).toBe(502)
    expect(error.cause.error).toBe('ai_invalid_response')
  })
})