From 29380c93f487b0f75e47d7902edc909aaf2b0286 Mon Sep 17 00:00:00 2001 From: drunkonjava Date: Thu, 31 Jul 2025 01:30:22 -0400 Subject: [PATCH] feat: Add core CI/CD infrastructure workflows ## Description Initial implementation of GitHub Actions CI/CD workflows for automated validation and testing. ## Part of - Original PR: #233 - Phase: 1 of 5 ## Changes - Add pr-validation.yml workflow for automated PR checks - Add tests.yml workflow for multi-device testing - Basic validation pipeline with build, lint, and test steps ## Testing - Workflows will be tested when PR is created - Includes iPhone and iPad test configurations ## Dependencies - None - this is the foundation for CI/CD --- .github/workflows/pr-validation.yml | 178 ++++++++++++++++++++ .github/workflows/tests.yml | 248 ++++++++++++++++++++++++++++ 2 files changed, 426 insertions(+) create mode 100644 .github/workflows/pr-validation.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 00000000..1dac2048 --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -0,0 +1,178 @@ +name: PR Validation + +on: + pull_request: + branches: [ main, develop ] + types: [ opened, synchronize, reopened, ready_for_review ] + +permissions: + contents: read + pull-requests: write + issues: write + +env: + XCODE_VERSION: '15.0' + SWIFT_VERSION: '5.9' + +jobs: + validate: + name: Validate Pull Request + runs-on: macos-14 + if: github.event.pull_request.draft == false + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: ${{ env.XCODE_VERSION }} + + - name: Cache Swift Package Manager + uses: actions/cache@v4 + with: + path: | + ~/Library/Developer/Xcode/DerivedData/**/SourcePackages + ~/Library/Caches/org.swift.swiftpm + .build + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved', 'project.yml') }} + restore-keys: | + ${{ runner.os }}-spm- + + - name: Install SwiftLint + run: | + if ! command -v swiftlint &> /dev/null; then + brew install swiftlint + fi + + - name: Run SwiftLint + run: | + if [ -f .swiftlint.yml ]; then + swiftlint lint --reporter github-actions-logging --config .swiftlint.yml + else + echo "No .swiftlint.yml found, running with default configuration" + swiftlint lint --reporter github-actions-logging || true + fi + + - name: Validate project structure + run: | + # Check if project.yml exists (XcodeGen project) + if [ ! -f "project.yml" ]; then + echo "::error::project.yml not found - this project uses XcodeGen" + exit 1 + fi + + # Verify all module directories exist + echo "Checking module structure..." + modules=("Foundation-Core" "Foundation-Models" "Foundation-Resources" "Infrastructure-Network" "Infrastructure-Storage" "Infrastructure-Security" "Infrastructure-Monitoring" "Services-Authentication" "Services-Business" "Services-External" "Services-Search" "Services-Sync" "UI-Core" "UI-Components" "UI-Styles" "Features-Inventory" "Features-Scanner" "Features-Settings" "Features-Analytics" "Features-Locations" "App-Main") + + for module in "${modules[@]}"; do + if [ ! -d "$module" ]; then + echo "::warning::Module directory $module not found" + else + echo "โœ“ $module exists" + fi + done + + - name: Generate Xcode project + run: | + if ! command -v xcodegen &> /dev/null; then + echo "Installing XcodeGen..." + brew install xcodegen + fi + xcodegen generate + + - name: Resolve Swift Package Dependencies + run: | + xcodebuild -resolvePackageDependencies \ + -project HomeInventoryModular.xcodeproj \ + -scheme HomeInventoryApp + + - name: Build for iOS Simulator + run: | + set -o pipefail + xcodebuild build \ + -project HomeInventoryModular.xcodeproj \ + -scheme HomeInventoryApp \ + -destination 'platform=iOS Simulator,name=iPhone 15 Pro,OS=17.0' \ + -configuration Debug \ + CODE_SIGNING_ALLOWED=NO + + - name: Check for compilation warnings + run: | + set -o pipefail + warnings=$(xcodebuild clean build \ + -project HomeInventoryModular.xcodeproj \ + -scheme HomeInventoryApp \ + -destination 'platform=iOS Simulator,name=iPhone 15 Pro,OS=17.0' \ + -configuration Debug \ + 2>&1 | grep -i warning | wc -l) + + echo "Total warnings: $warnings" + if [ "$warnings" -gt 50 ]; then + echo "::warning::High number of compilation warnings ($warnings). Consider addressing them." + fi + + - name: Validate module boundaries + run: | + if [ -f "scripts/validate-module-dependencies.sh" ]; then + chmod +x scripts/validate-module-dependencies.sh + ./scripts/validate-module-dependencies.sh || echo "::warning::Module dependency validation failed" + else + echo "Module dependency validation script not found" + fi + + - name: Check for TODO and FIXME comments + run: | + todos=$(find . -name "*.swift" -not -path "./.*" -exec grep -n "TODO\|FIXME" {} + | wc -l) + echo "Found $todos TODO/FIXME comments" + if [ "$todos" -gt 100 ]; then + echo "::warning::High number of TODO/FIXME comments ($todos). Consider addressing some before merging." + fi + + - name: Security checks + run: | + # Check for potential security issues + echo "Running basic security checks..." + + # Check for hardcoded secrets (basic patterns) + if grep -r -i "password\s*=\s*\"" --include="*.swift" . | grep -v "placeholder\|example\|test"; then + echo "::warning::Potential hardcoded passwords found" + fi + + if grep -r -i "api[_-]?key\s*=\s*\"" --include="*.swift" . | grep -v "placeholder\|example\|test"; then + echo "::warning::Potential hardcoded API keys found" + fi + + # Check for SQL injection vulnerabilities + if grep -r "\".*SELECT.*\\\(.*\\\).*\"" --include="*.swift" .; then + echo "::warning::Potential SQL injection vulnerability found" + fi + + - name: Report PR Status + if: always() + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const { number } = context.issue; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: number, + body: `## ๐Ÿ” PR Validation Results + + **Build Status:** ${{ job.status == 'success' && 'โœ… Passed' || 'โŒ Failed' }} + **SwiftLint:** ${{ steps.swiftlint.outcome == 'success' && 'โœ… Passed' || 'โš ๏ธ Issues found' }} + **Project Structure:** ${{ steps.validate.outcome == 'success' && 'โœ… Valid' || 'โŒ Issues found' }} + **Compilation:** ${{ steps.build.outcome == 'success' && 'โœ… Success' || 'โŒ Failed' }} + + ${context.payload.pull_request.mergeable === false ? 'โš ๏ธ **Merge conflicts detected** - Please resolve before merging' : ''} + + --- + *This comment was automatically generated by the PR validation workflow.*` + }); \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..9fbe9e39 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,248 @@ +name: Automated Testing + +on: + pull_request: + branches: [ main, develop ] + types: [ opened, synchronize, reopened, ready_for_review ] + push: + branches: [ main, develop ] + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + issues: write + +env: + XCODE_VERSION: '15.0' + SWIFT_VERSION: '5.9' + +jobs: + test: + name: Run Tests + runs-on: macos-14 + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false + + strategy: + matrix: + destination: + - platform=iOS Simulator,name=iPhone 15 Pro,OS=17.0 + - platform=iOS Simulator,name=iPad Pro (12.9-inch) (6th generation),OS=17.0 + include: + - destination: platform=iOS Simulator,name=iPhone 15 Pro,OS=17.0 + device: iPhone + - destination: platform=iOS Simulator,name=iPad Pro (12.9-inch) (6th generation),OS=17.0 + device: iPad + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: ${{ env.XCODE_VERSION }} + + - name: Cache Swift Package Manager + uses: actions/cache@v4 + with: + path: | + ~/Library/Developer/Xcode/DerivedData/**/SourcePackages + ~/Library/Caches/org.swift.swiftpm + .build + key: ${{ runner.os }}-spm-tests-${{ hashFiles('**/Package.resolved', 'project.yml') }} + restore-keys: | + ${{ runner.os }}-spm-tests- + ${{ runner.os }}-spm- + + - name: Install dependencies + run: | + if ! command -v xcodegen &> /dev/null; then + echo "Installing XcodeGen..." + brew install xcodegen + fi + + if ! command -v xcpretty &> /dev/null; then + echo "Installing xcpretty..." + gem install xcpretty + fi + + - name: Generate Xcode project + run: xcodegen generate + + - name: Resolve Swift Package Dependencies + run: | + xcodebuild -resolvePackageDependencies \ + -project HomeInventoryModular.xcodeproj \ + -scheme HomeInventoryApp + + - name: List available test schemes + run: | + echo "Available schemes:" + xcodebuild -list -project HomeInventoryModular.xcodeproj | grep -A 20 "Schemes:" || true + + - name: Check test targets + id: check_tests + run: | + # Check if UI test target exists and is buildable + if xcodebuild -showBuildSettings -project HomeInventoryModular.xcodeproj -scheme HomeInventoryModularUITests 2>/dev/null | grep -q "PRODUCT_NAME"; then + echo "ui_tests_available=true" >> $GITHUB_OUTPUT + else + echo "ui_tests_available=false" >> $GITHUB_OUTPUT + echo "::warning::UI test target not available or not buildable" + fi + + # Check if unit test targets exist - Currently no unit test scheme available + echo "unit_tests_available=false" >> $GITHUB_OUTPUT + echo "::warning::No unit test schemes currently configured" + + - name: Run Unit Tests + if: steps.check_tests.outputs.unit_tests_available == 'true' + run: | + # Unit tests currently disabled as no unit test schemes are configured + echo "::notice::Unit tests skipped - no unit test schemes available" + continue-on-error: true + + - name: Run UI Tests + if: steps.check_tests.outputs.ui_tests_available == 'true' + run: | + set -o pipefail + xcodebuild test \ + -project HomeInventoryModular.xcodeproj \ + -scheme HomeInventoryModularUITests \ + -destination '${{ matrix.destination }}' \ + -enableCodeCoverage YES \ + -resultBundlePath TestResults-UI-${{ matrix.device }}.xcresult \ + | xcpretty --test --color + continue-on-error: true + + - name: Run Accessibility Tests + run: | + # Run accessibility tests if available + if [ -f "HomeInventoryModularUITests/AccessibilityUITests.swift" ]; then + echo "Running accessibility tests..." + set -o pipefail + xcodebuild test \ + -project HomeInventoryModular.xcodeproj \ + -scheme HomeInventoryModularUITests \ + -destination '${{ matrix.destination }}' \ + -testPlan AccessibilityTests \ + -resultBundlePath TestResults-A11y-${{ matrix.device }}.xcresult \ + | xcpretty --test --color || echo "::warning::Accessibility tests failed or not configured" + else + echo "::notice::No accessibility tests found" + fi + continue-on-error: true + + - name: Test Module Compilation + run: | + # Test individual module compilation to catch module-specific issues + modules=("Foundation-Core" "Foundation-Models" "Infrastructure-Network" "Infrastructure-Storage" "UI-Components") + + for module in "${modules[@]}"; do + if [ -d "$module" ]; then + echo "Testing compilation of $module..." + if [ -f "$module/Package.swift" ]; then + cd "$module" + swift build || echo "::warning::Module $module failed to compile as SPM package" + cd .. + fi + fi + done + + - name: Upload Test Results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results-${{ matrix.device }} + path: | + TestResults-*.xcresult + retention-days: 5 + + - name: Generate Coverage Report + if: matrix.device == 'iPhone' + run: | + # Generate coverage report from xcresult files + if ls TestResults-*.xcresult 1> /dev/null 2>&1; then + echo "Generating coverage report..." + + # Use xcov if available, otherwise use built-in xcodebuild coverage + if command -v xcov &> /dev/null; then + xcov -x TestResults-Unit-iPhone.xcresult -o coverage/ --minimum_coverage_percentage 60 + else + echo "xcov not available, extracting basic coverage data..." + for result in TestResults-*.xcresult; do + echo "Coverage data from $result:" + xcrun xccov view --report "$result" || true + done + fi + else + echo "::warning::No test results found for coverage analysis" + fi + continue-on-error: true + + - name: Upload Coverage Report + if: matrix.device == 'iPhone' + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: | + coverage/ + *.coverage + retention-days: 10 + + - name: Comment Test Results + if: github.event_name == 'pull_request' && matrix.device == 'iPhone' + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const { number } = context.issue; + + // Simple test status - in a real implementation, you'd parse the xcresult files + const testStatus = '${{ job.status }}' === 'success' ? 'โœ… Passed' : 'โŒ Failed'; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: number, + body: `## ๐Ÿงช Test Results + + **iPhone Tests:** ${testStatus} + **iPad Tests:** ${{ job.status == 'success' && 'โœ… Passed' || 'โŒ Failed' }} + **Coverage:** See artifacts for detailed report + + **Test Summary:** + - Unit Tests: ${{ steps.check_tests.outputs.unit_tests_available == 'true' && 'Executed' || 'Skipped (not available)' }} + - UI Tests: ${{ steps.check_tests.outputs.ui_tests_available == 'true' && 'Executed' || 'Skipped (not available)' }} + - Accessibility Tests: ${{ contains(steps.*.outcome, 'success') && 'Executed' || 'Skipped' }} + + ${testStatus.includes('Failed') ? 'โš ๏ธ Some tests failed - please check the detailed results in the Actions tab.' : ''} + + --- + *View detailed results and coverage reports in the [Actions tab](https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id }}).*` + }); + continue-on-error: true + + test-summary: + name: Test Summary + runs-on: ubuntu-latest + needs: test + if: always() + + steps: + - name: Generate summary + run: | + echo "## Test Execution Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Device | Status |" >> $GITHUB_STEP_SUMMARY + echo "|--------|---------|" >> $GITHUB_STEP_SUMMARY + echo "| iPhone | ${{ contains(needs.test.result, 'success') && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "| iPad | ${{ contains(needs.test.result, 'success') && 'โœ… Passed' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Overall Result:** ${{ needs.test.result == 'success' && 'โœ… All tests passed' || 'โŒ Some tests failed' }}" >> $GITHUB_STEP_SUMMARY + + # Status checks are automatically created by GitHub Actions + # Manual status check creation removed due to permission requirements \ No newline at end of file