Ctrl K
ComunidadGuías…

proxy.test.ts

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

Archivo original ↗Preguntar a KiBot
import { NextRequest, NextResponse } from 'next/server'

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import { betterAuthProvider } from '@/lib/auth/providers/better-auth'
import { updateSession } from '@/lib/supabase/middleware'

import { proxy } from './proxy'

vi.mock('@/lib/supabase/middleware', () => ({
  updateSession: vi.fn()
}))

const AUTH_ENV_KEYS = [
  'AUTH_PROVIDER',
  'ENABLE_AUTH',
  'NEXT_PUBLIC_SUPABASE_URL',
  'NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY'
] as const

describe('proxy', () => {
  const originalEnv: Record<string, string | undefined> = {}

  beforeEach(() => {
    vi.clearAllMocks()
    for (const key of AUTH_ENV_KEYS) {
      originalEnv[key] = process.env[key]
      delete process.env[key]
    }
  })

  afterEach(() => {
    for (const key of AUTH_ENV_KEYS) {
      if (originalEnv[key] === undefined) {
        delete process.env[key]
      } else {
        process.env[key] = originalEnv[key]
      }
    }
  })

  it('passes through in anonymous mode (ENABLE_AUTH=false)', async () => {
    process.env.ENABLE_AUTH = 'false'

    const request = new NextRequest('http://localhost:3000/search/abc')
    const response = await proxy(request)

    expect(updateSession).not.toHaveBeenCalled()
    expect(response.status).toBe(200)
    expect(response.headers.get('location')).toBeNull()
    expect(response.headers.get('x-url')).toBe(
      'http://localhost:3000/search/abc'
    )
  })

  it('passes through when Supabase is not configured', async () => {
    const request = new NextRequest('http://localhost:3000/search/abc')
    const response = await proxy(request)

    expect(updateSession).not.toHaveBeenCalled()
    expect(response.status).toBe(200)
  })

  it('delegates session enforcement to updateSession when Supabase is configured', async () => {
    process.env.NEXT_PUBLIC_SUPABASE_URL = 'https://example.supabase.co'
    process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY = 'sb_publishable_key'
    vi.mocked(updateSession).mockResolvedValue(NextResponse.next())

    const request = new NextRequest('http://localhost:3000/search/abc')
    await proxy(request)

    expect(updateSession).toHaveBeenCalledTimes(1)
  })

  describe('PostHog relay', () => {
    it('rewrites API paths straight to PostHog without credentials', async () => {
      const request = new NextRequest('http://localhost:3000/relay/e/?ip=0', {
        method: 'POST',
        headers: {
          cookie: 'sb-access-token=abc',
          authorization: 'Bearer token',
          'cf-access-jwt-assertion': 'gateway-token',
          'x-forwarded-for': '203.0.113.7',
          'user-agent': 'Mozilla/5.0 (test)'
        }
      })
      const response = await proxy(request)

      expect(response.headers.get('x-middleware-rewrite')).toBe(
        'https://us.i.posthog.com/e/?ip=0'
      )
      const forwarded = response.headers.get('x-middleware-override-headers')
      expect(forwarded?.split(',').sort()).toEqual([
        'user-agent',
        'x-forwarded-for'
      ])
      expect(updateSession).not.toHaveBeenCalled()
    })

    it('rewrites static and array paths to the asset host', async () => {
      const response = await proxy(
        new NextRequest('http://localhost:3000/relay/static/array.js')
      )

      expect(response.headers.get('x-middleware-rewrite')).toBe(
        'https://us-assets.i.posthog.com/static/array.js'
      )
    })

    it('rewrites in anonymous mode too', async () => {
      process.env.ENABLE_AUTH = 'false'
      const response = await proxy(
        new NextRequest('http://localhost:3000/relay/flags/?v=2')
      )

      expect(response.headers.get('x-middleware-rewrite')).toBe(
        'https://us.i.posthog.com/flags/?v=2'
      )
    })

    it('refuses paths that resolve off the PostHog origin', async () => {
      const response = await proxy(
        new NextRequest('http://localhost:3000/relay//evil.example/x')
      )

      expect(response.status).toBe(404)
      expect(response.headers.get('x-middleware-rewrite')).toBeNull()
    })

    it('refuses declared payloads above the cap', async () => {
      const response = await proxy(
        new NextRequest('http://localhost:3000/relay/e/', {
          method: 'POST',
          headers: { 'content-length': String(64 * 1024 * 1024) },
          body: '{}'
        })
      )

      expect(response.status).toBe(413)
      expect(response.headers.get('x-middleware-rewrite')).toBeNull()
    })

    it('leaves /relay to the route handler under Better Auth', async () => {
      process.env.AUTH_PROVIDER = 'better-auth'
      const handleSession = vi
        .spyOn(betterAuthProvider, 'handleSession')
        .mockResolvedValue(NextResponse.next())

      const response = await proxy(
        new NextRequest('http://localhost:3000/relay/e/')
      )

      expect(handleSession).toHaveBeenCalledTimes(1)
      expect(response.headers.get('x-middleware-rewrite')).toBeNull()
    })

    it('does not treat lookalike paths as the relay', async () => {
      const response = await proxy(
        new NextRequest('http://localhost:3000/relayed')
      )

      expect(response.headers.get('x-middleware-rewrite')).toBeNull()
    })
  })
})