next.js/test/production/app-dir/upload-trace/upload-trace.test.ts
upload-trace.test.ts148 lines4.2 KB
import { nextTestSetup, isNextStart } from 'e2e-utils'
import { runNextCommand } from 'next-test-utils'
import http from 'http'
import path from 'path'
import fs from 'fs'

describe('upload-trace', () => {
  if (!isNextStart) {
    it('skipped for non-start mode', () => {})
    return
  }

  const { next, isTurbopack, skipped } = nextTestSetup({
    files: __dirname,
    skipStart: true,
    skipDeployment: true,
    buildCommand: 'pnpm next build --experimental-cpu-prof --internal-trace',
  })

  if (skipped) return

  it('should upload profiles and trace to the mock endpoint after build', async () => {
    const buildResult = await next.build()
    expect(buildResult.exitCode).toBe(0)

    const profilesDir = path.join(next.testDir, '.next-profiles')
    expect(fs.existsSync(profilesDir)).toBe(true)

    const allFiles = fs.readdirSync(profilesDir)
    const cpuProfiles = allFiles.filter((f: string) =>
      f.endsWith('.cpuprofile')
    )
    expect(cpuProfiles.length).toBeGreaterThan(0)

    if (isTurbopack) {
      expect(allFiles).toContain('trace-turbopack')
    }

    const uploadableFiles = allFiles.filter(
      (f: string) => f.endsWith('.cpuprofile') || f === 'trace-turbopack'
    )
    const expectedUploadCount = uploadableFiles.length

    const handshakeRequests: any[] = []

    const mockServer = http.createServer(async (req, res) => {
      const chunks: Buffer[] = []
      for await (const chunk of req) {
        chunks.push(chunk as Buffer)
      }
      const body = Buffer.concat(chunks).toString('utf-8')

      let parsed: any = null
      try {
        parsed = JSON.parse(body)
      } catch {
        // Not JSON — binary blob upload
      }

      // Handle the upload-trace token handshake (sends { filename })
      if (parsed && parsed.filename) {
        handshakeRequests.push(parsed)

        res.writeHead(200, { 'content-type': 'application/json' })
        res.end(
          JSON.stringify({
            clientToken: 'vercel_blob_client_TESTSTOREID_dGVzdC5wYXlsb2Fk',
            pathname: `profiles/${parsed.filename}`,
            sessionId: 'test-session-id',
            sessionToken: 'test-session-token',
          })
        )
        return
      }

      // Handle @vercel/blob put() — the actual blob upload
      res.writeHead(200, { 'content-type': 'application/json' })
      res.end(
        JSON.stringify({
          url: 'https://test.blob.vercel-storage.com/profiles/test',
          downloadUrl:
            'https://test.blob.vercel-storage.com/profiles/test?download=1',
          pathname: 'profiles/test',
          contentType: 'application/octet-stream',
          contentDisposition: 'attachment; filename="test"',
        })
      )
    })

    await new Promise<void>((resolve) => {
      mockServer.listen(0, '127.0.0.1', resolve)
    })

    const address = mockServer.address() as { port: number }
    const mockUrl = `http://127.0.0.1:${address.port}`

    try {
      const result = await runNextCommand(['internal', 'upload-trace'], {
        cwd: next.testDir,
        stderr: true,
        stdout: true,
        env: {
          __NEXT_UPLOAD_TRACE_URL_OVERRIDE: mockUrl,
          VERCEL_BLOB_API_URL: mockUrl,
        },
      })

      if (result.code !== 0) {
        console.log('upload-trace stdout:', result.stdout)
        console.log('upload-trace stderr:', result.stderr)
      }

      expect(result.code).toBe(0)
      expect(handshakeRequests.length).toBe(expectedUploadCount)
      for (const req of handshakeRequests) {
        expect(req.filename).toBeTruthy()
      }
      expect(result.stdout + result.stderr).toContain(
        'All files uploaded successfully'
      )
    } finally {
      mockServer.close()
    }
  })

  it('should fail gracefully when no profiles directory exists', async () => {
    const emptyDir = path.join(next.testDir, 'empty-project')
    fs.mkdirSync(emptyDir, { recursive: true })

    const result = await runNextCommand(
      ['internal', 'upload-trace', emptyDir],
      {
        cwd: next.testDir,
        stderr: true,
        stdout: true,
        env: {
          __NEXT_UPLOAD_TRACE_URL_OVERRIDE: 'http://127.0.0.1:1',
        },
      }
    )

    expect(result.code).toBe(1)
    expect(result.stdout + result.stderr).toContain(
      'Profiles directory not found'
    )
  })
})
Quest for Codev2.0.0
/
SIGN IN