test-apifox-connection.js 8.25 KB
#!/usr/bin/env node

/**
 * Apifox 连接测试工具
 *
 * 功能:
 * 1. 验证 .env.apifox 配置是否正确
 * 2. 测试 Apifox API 连接
 * 3. 显示项目基本信息
 * 4. 列出所有接口数量
 *
 * 使用:
 * node scripts/test-apifox-connection.js
 */

const fs = require('fs');
const path = require('path');
const https = require('https');

// 颜色输出
const colors = {
  reset: '\x1b[0m',
  bright: '\x1b[1m',
  green: '\x1b[32m',
  yellow: '\x1b[33m',
  red: '\x1b[31m',
  blue: '\x1b[34m',
  cyan: '\x1b[36m'
};

function log(message, color = 'reset') {
  console.log(`${colors[color]}${message}${colors.reset}`);
}

// 加载配置
function loadConfig() {
  const envPath = path.join(__dirname, '../.env.apifox');

  if (!fs.existsSync(envPath)) {
    log('❌ 未找到 .env.apifox 文件', 'red');
    log('\n📝 请先创建配置文件:', 'yellow');
    log('   cp .env.apifox.example .env.apifox', 'blue');
    log('   然后编辑 .env.apifox 填写凭证信息', 'blue');
    return null;
  }

  const env = fs.readFileSync(envPath, 'utf-8');
  const config = {};

  env.split('\n').forEach(line => {
    const [key, ...valueParts] = line.split('=');
    const value = valueParts.join('=');
    if (key && !key.startsWith('#') && value) {
      config[key.trim()] = value.trim();
    }
  });

  if (!config.VITE_APIFOX_TOKEN || !config.VITE_APIFOX_PROJECT_ID) {
    log('❌ .env.apifox 文件中缺少必要的配置', 'red');
    log('\n请确保填写了以下配置:', 'yellow');
    log('   VITE_APIFOX_TOKEN=aps-your_token_here', 'blue');
    log('   VITE_APIFOX_PROJECT_ID=your_project_id_here', 'blue');
    return null;
  }

  return config;
}

// 验证 Token 格式
function validateToken(token) {
  // Apifox Token 格式:APS-xxxxxxxx (大写)
  if (!token.toUpperCase().startsWith('APS-')) {
    log('⚠️  Token 格式不正确', 'yellow');
    log('   正确格式: APS-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'cyan');
    return false;
  }

  if (token.length < 10) {
    log('⚠️  Token 长度似乎不正确', 'yellow');
    return false;
  }

  return true;
}

// 验证项目 ID 格式
function validateProjectId(projectId) {
  if (!/^\d+$/.test(projectId)) {
    log('⚠️  项目 ID 格式不正确', 'yellow');
    log('   正确格式: 纯数字(如: 12345678)', 'cyan');
    return false;
  }

  return true;
}

// 发送 HTTPS 请求
function httpsRequest(options) {
  return new Promise((resolve, reject) => {
    log(`📡 发送请求到: https://${options.hostname}${options.path}`, 'blue');

    const req = https.request(options, (res) => {
      const chunks = [];

      res.on('data', chunk => {
        chunks.push(chunk);
      });

      res.on('end', () => {
        const raw = Buffer.concat(chunks).toString('utf8').trim();

        if (!raw) {
          if (res.statusCode >= 200 && res.statusCode < 300) {
            resolve({ data: null, total: 0, __empty: true, statusCode: res.statusCode });
            return;
          }

          reject(new Error(`HTTP ${res.statusCode}: Empty response`));
          return;
        }

        try {
          const json = JSON.parse(raw.replace(/^\uFEFF/, ''));
          if (res.statusCode === 200) {
            resolve(json);
          } else {
            reject(new Error(`HTTP ${res.statusCode}: ${json.message || raw}`));
          }
        } catch (err) {
          reject(new Error(`解析响应失败: ${err.message}`));
        }
      });
    });

    req.on('error', reject);
    req.end();
  });
}

// 测试获取项目信息
async function testProjectInfo(config) {
  log('\n📊 获取项目信息...', 'cyan');

  const options = {
    hostname: 'api.apifox.com',
    path: `/api/v1/projects/${config.VITE_APIFOX_PROJECT_ID}`,
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`,
      'Content-Type': 'application/json'
    }
  };

  try {
    const response = await httpsRequest(options);
    if (!response || !response.data) {
      throw new Error('响应为空或缺少 data 字段');
    }

    const project = response.data;

    log('✅ 项目信息获取成功', 'green');
    log('');
    log('项目详情:', 'bright');
    log(`  名称: ${project.name || '未设置'}`, 'reset');
    log(`  ID: ${project.id}`, 'reset');
    log(`  描述: ${project.description || '无'}`, 'reset');
    log(`  创建时间: ${project.createdTime || '未知'}`, 'reset');

    return true;
  } catch (err) {
    log(`❌ 获取项目信息失败: ${err.message}`, 'red');

    if (err.message.includes('401')) {
      log('\n可能的原因:', 'yellow');
      log('  1. API Token 错误或已过期', 'yellow');
      log('  2. 请检查 .env.apifox 中的 VITE_APIFOX_TOKEN', 'yellow');
    } else if (err.message.includes('403') || err.message.includes('404')) {
      log('\n可能的原因:', 'yellow');
      log('  1. 项目 ID 错误', 'yellow');
      log('  2. 账号无该项目的访问权限', 'yellow');
      log('  3. 请检查 .env.apifox 中的 VITE_APIFOX_PROJECT_ID', 'yellow');
    }

    return false;
  }
}

// 测试获取接口列表
async function testApiList(config) {
  log('\n📋 获取接口列表...', 'cyan');

  const options = {
    hostname: 'api.apifox.com',
    path: `/api/v1/projects/${config.VITE_APIFOX_PROJECT_ID}/apis?pageSize=10`,
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${config.VITE_APIFOX_TOKEN}`,
      'Content-Type': 'application/json'
    }
  };

  try {
    const response = await httpsRequest(options);
    const apis = Array.isArray(response.data) ? response.data : [];

    log('✅ 接口列表获取成功', 'green');
    log(`  共找到 ${response.total || apis.length} 个接口`, 'blue');

    if (apis.length > 0) {
      log('\n前 10 个接口:', 'bright');
      apis.forEach((api, index) => {
        const method = (api.attributes?.method || 'GET').toUpperCase();
        const path = api.attributes?.path || '/';
        const name = api.attributes?.name || api.id;

        log(`  ${index + 1}. ${method} ${path}`, 'cyan');
        log(`     ${name}`, 'reset');
      });

      if (response.total > 10) {
        log(`\n  ... 还有 ${response.total - 10} 个接口`, 'yellow');
      }
    } else if (response.__empty) {
      log('⚠️  接口列表响应为空,已按空列表处理', 'yellow');
    }

    return true;
  } catch (err) {
    log(`❌ 获取接口列表失败: ${err.message}`, 'red');
    return false;
  }
}

// 主函数
async function main() {
  log('\n🧪 Apifox 连接测试工具', 'bright');
  log('=' .repeat(50), 'bright');

  // 1. 加载配置
  log('\n📝 步骤 1/4: 加载配置文件', 'cyan');
  const config = loadConfig();
  if (!config) {
    process.exit(1);
  }

  log('✅ 配置文件加载成功', 'green');
  log(`  Token: ${config.VITE_APIFOX_TOKEN.substring(0, 15)}...`, 'blue');
  log(`  项目 ID: ${config.VITE_APIFOX_PROJECT_ID}`, 'blue');

  // 2. 验证格式
  log('\n🔍 步骤 2/4: 验证配置格式', 'cyan');

  let valid = true;

  if (!validateToken(config.VITE_APIFOX_TOKEN)) {
    valid = false;
  }

  if (!validateProjectId(config.VITE_APIFOX_PROJECT_ID)) {
    valid = false;
  }

  if (!valid) {
    log('\n❌ 配置验证失败,请修正后重试', 'red');
    process.exit(1);
  }

  log('✅ 配置格式验证通过', 'green');

  // 3. 测试项目信息
  log('\n🌐 步骤 3/4: 测试 API 连接', 'cyan');
  const projectSuccess = await testProjectInfo(config);

  if (!projectSuccess) {
    log('\n❌ API 连接测试失败', 'red');
    log('\n💡 建议:', 'yellow');
    log('  1. 检查网络连接', 'yellow');
    log('  2. 验证 Token 和项目 ID 是否正确', 'yellow');
    log('  3. 确认账号有该项目的访问权限', 'yellow');
    process.exit(1);
  }

  // 4. 测试接口列表
  const apiSuccess = await testApiList(config);

  if (!apiSuccess) {
    log('\n⚠️  接口列表获取失败,但项目连接正常', 'yellow');
    process.exit(1);
  }

  // 完成
  log('\n' + '='.repeat(50), 'bright');
  log('✅ 所有测试通过!', 'green');
  log('\n🎉 配置正确,可以开始同步 API 了', 'green');
  log('\n运行以下命令同步 API:', 'blue');
  log('  pnpm api:sync', 'bright');
  log('');
}

// 运行
main().catch(err => {
  log(`\n❌ 测试失败: ${err.message}`, 'red');
  console.error(err);
  process.exit(1);
});