name: SearchEngine Test Pipeline on: push: branches: [ main, master, develop ] pull_request: branches: [ main, master, develop ] workflow_dispatch: # 允许手动触发 env: PYTHON_VERSION: '3.9' NODE_VERSION: '16' jobs: # 代码质量检查 code-quality: runs-on: ubuntu-latest name: Code Quality Check steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 black isort mypy pylint pip install -r requirements.txt - name: Run Black (code formatting) run: | black --check --diff . - name: Run isort (import sorting) run: | isort --check-only --diff . - name: Run Flake8 (linting) run: | flake8 --max-line-length=100 --ignore=E203,W503 . - name: Run MyPy (type checking) run: | mypy --ignore-missing-imports --no-strict-optional . - name: Run Pylint run: | pylint --disable=C0114,C0115,C0116 --errors-only . # 单元测试 unit-tests: runs-on: ubuntu-latest name: Unit Tests strategy: matrix: python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Cache pip dependencies uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip pip install pytest pytest-cov pytest-json-report pip install -r requirements.txt - name: Create test logs directory run: mkdir -p test_logs - name: Run unit tests run: | python -m pytest tests/unit/ \ -v \ --tb=short \ --cov=. \ --cov-report=xml \ --cov-report=html \ --cov-report=term-missing \ --json-report \ --json-report-file=test_logs/unit_test_results.json - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: unittests name: codecov-umbrella - name: Upload unit test results uses: actions/upload-artifact@v3 if: always() with: name: unit-test-results-${{ matrix.python-version }} path: | test_logs/unit_test_results.json htmlcov/ # 集成测试 integration-tests: runs-on: ubuntu-latest name: Integration Tests needs: [code-quality, unit-tests] services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.8.0 env: discovery.type: single-node ES_JAVA_OPTS: -Xms1g -Xmx1g xpack.security.enabled: false ports: - 9200:9200 options: >- --health-cmd "curl http://localhost:9200/_cluster/health" --health-interval 10s --health-timeout 5s --health-retries 10 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y curl - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install pytest pytest-json-report httpx pip install -r requirements.txt - name: Create test logs directory run: mkdir -p test_logs - name: Wait for Elasticsearch run: | echo "Waiting for Elasticsearch to be ready..." for i in {1..30}; do if curl -s http://localhost:9200/_cluster/health | grep -q '"status":"green\|yellow"'; then echo "Elasticsearch is ready" break fi echo "Attempt $i/30: Elasticsearch not ready yet" sleep 2 done - name: Setup test index run: | curl -X PUT http://localhost:9200/test_products \ -H 'Content-Type: application/json' \ -d '{ "settings": { "number_of_shards": 1, "number_of_replicas": 0 }, "mappings": { "properties": { "name": {"type": "text"}, "brand_name": {"type": "text"}, "tags": {"type": "text"}, "price": {"type": "double"}, "category_id": {"type": "integer"}, "spu_id": {"type": "keyword"}, "text_embedding": {"type": "dense_vector", "dims": 1024} } } }' - name: Insert test data run: | curl -X POST http://localhost:9200/test_products/_bulk \ -H 'Content-Type: application/json' \ --data-binary @- << 'EOF' {"index": {"_id": "1"}} {"name": "红色连衣裙", "brand_name": "测试品牌", "tags": ["红色", "连衣裙", "女装"], "price": 299.0, "category_id": 1, "spu_id": "dress_001"} {"index": {"_id": "2"}} {"name": "蓝色连衣裙", "brand_name": "测试品牌", "tags": ["蓝色", "连衣裙", "女装"], "price": 399.0, "category_id": 1, "spu_id": "dress_002"} {"index": {"_id": "3"}} {"name": "智能手机", "brand_name": "科技品牌", "tags": ["智能", "手机", "数码"], "price": 2999.0, "category_id": 2, "spu_id": "phone_001"} EOF - name: Run integration tests env: ES_HOST: http://localhost:9200 CUSTOMER_ID: test_customer TESTING_MODE: true run: | python -m pytest tests/integration/ \ -v \ --tb=short \ -m "not slow" \ --json-report \ --json-report-file=test_logs/integration_test_results.json - name: Upload integration test results uses: actions/upload-artifact@v3 if: always() with: name: integration-test-results path: test_logs/integration_test_results.json # API测试 api-tests: runs-on: ubuntu-latest name: API Tests needs: [code-quality, unit-tests] services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.8.0 env: discovery.type: single-node ES_JAVA_OPTS: -Xms1g -Xmx1g xpack.security.enabled: false ports: - 9200:9200 options: >- --health-cmd "curl http://localhost:9200/_cluster/health" --health-interval 10s --health-timeout 5s --health-retries 10 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y curl - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install pytest pytest-json-report httpx pip install -r requirements.txt - name: Create test logs directory run: mkdir -p test_logs - name: Wait for Elasticsearch run: | echo "Waiting for Elasticsearch to be ready..." for i in {1..30}; do if curl -s http://localhost:9200/_cluster/health | grep -q '"status":"green\|yellow"'; then echo "Elasticsearch is ready" break fi echo "Attempt $i/30: Elasticsearch not ready yet" sleep 2 done - name: Setup test index and data run: | # 创建索引 curl -X PUT http://localhost:9200/test_products \ -H 'Content-Type: application/json' \ -d '{ "settings": {"number_of_shards": 1, "number_of_replicas": 0}, "mappings": { "properties": { "name": {"type": "text"}, "brand_name": {"type": "text"}, "tags": {"type": "text"}, "price": {"type": "double"}, "category_id": {"type": "integer"}, "spu_id": {"type": "keyword"}, "text_embedding": {"type": "dense_vector", "dims": 1024} } } }' # 插入测试数据 curl -X POST http://localhost:9200/test_products/_bulk \ -H 'Content-Type: application/json' \ --data-binary @- << 'EOF' {"index": {"_id": "1"}} {"name": "红色连衣裙", "brand_name": "测试品牌", "tags": ["红色", "连衣裙", "女装"], "price": 299.0, "category_id": 1, "spu_id": "dress_001"} {"index": {"_id": "2"}} {"name": "蓝色连衣裙", "brand_name": "测试品牌", "tags": ["蓝色", "连衣裙", "女装"], "price": 399.0, "category_id": 1, "spu_id": "dress_002"} EOF - name: Start API service env: ES_HOST: http://localhost:9200 CUSTOMER_ID: test_customer API_HOST: 127.0.0.1 API_PORT: 6003 TESTING_MODE: true run: | python -m api.app \ --host $API_HOST \ --port $API_PORT \ --customer $CUSTOMER_ID \ --es-host $ES_HOST & echo $! > api.pid # 等待API服务启动 for i in {1..30}; do if curl -s http://$API_HOST:$API_PORT/health > /dev/null; then echo "API service is ready" break fi echo "Attempt $i/30: API service not ready yet" sleep 2 done - name: Run API tests env: ES_HOST: http://localhost:9200 API_HOST: 127.0.0.1 API_PORT: 6003 CUSTOMER_ID: test_customer TESTING_MODE: true run: | python -m pytest tests/integration/test_api_integration.py \ -v \ --tb=short \ --json-report \ --json-report-file=test_logs/api_test_results.json - name: Stop API service if: always() run: | if [ -f api.pid ]; then kill $(cat api.pid) || true rm api.pid fi - name: Upload API test results uses: actions/upload-artifact@v3 if: always() with: name: api-test-results path: test_logs/api_test_results.json # 性能测试 performance-tests: runs-on: ubuntu-latest name: Performance Tests needs: [code-quality, unit-tests] if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.8.0 env: discovery.type: single-node ES_JAVA_OPTS: -Xms2g -Xmx2g xpack.security.enabled: false ports: - 9200:9200 options: >- --health-cmd "curl http://localhost:9200/_cluster/health" --health-interval 10s --health-timeout 5s --health-retries 10 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install pytest locust pip install -r requirements.txt - name: Wait for Elasticsearch run: | echo "Waiting for Elasticsearch to be ready..." for i in {1..30}; do if curl -s http://localhost:9200/_cluster/health | grep -q '"status":"green\|yellow"'; then echo "Elasticsearch is ready" break fi sleep 2 done - name: Setup test data run: | # 创建并填充测试索引 python scripts/create_test_data.py --count 1000 - name: Run performance tests env: ES_HOST: http://localhost:9200 TESTING_MODE: true run: | python scripts/run_performance_tests.py - name: Upload performance results uses: actions/upload-artifact@v3 if: always() with: name: performance-test-results path: performance_results/ # 安全扫描 security-scan: runs-on: ubuntu-latest name: Security Scan needs: [code-quality] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install security scanning tools run: | python -m pip install --upgrade pip pip install safety bandit - name: Run Safety (dependency check) run: | safety check --json --output safety_report.json || true - name: Run Bandit (security linter) run: | bandit -r . -f json -o bandit_report.json || true - name: Upload security reports uses: actions/upload-artifact@v3 if: always() with: name: security-reports path: | safety_report.json bandit_report.json # 测试结果汇总 test-summary: runs-on: ubuntu-latest name: Test Summary needs: [unit-tests, integration-tests, api-tests, security-scan] if: always() steps: - name: Checkout code uses: actions/checkout@v4 - name: Download all test artifacts uses: actions/download-artifact@v3 - name: Generate test summary run: | python scripts/generate_test_summary.py - name: Upload final report uses: actions/upload-artifact@v3 with: name: final-test-report path: final_test_report.* - name: Comment PR with results if: github.event_name == 'pull_request' uses: actions/github-script@v6 with: script: | const fs = require('fs'); // 读取测试报告 let reportContent = ''; try { reportContent = fs.readFileSync('final_test_report.txt', 'utf8'); } catch (e) { console.log('Could not read report file'); return; } // 提取摘要信息 const lines = reportContent.split('\n'); let summary = ''; let inSummary = false; for (const line of lines) { if (line.includes('测试摘要')) { inSummary = true; continue; } if (inSummary && line.includes('测试套件详情')) { break; } if (inSummary && line.trim()) { summary += line + '\n'; } } // 构建评论内容 const comment = `## 🧪 测试报告\n\n${summary}\n\n详细的测试报告请查看 [Artifacts](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) 部分。`; // 发送评论 github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: comment });