diff --git a/.github/workflows/backend/test-backend.yml b/.github/workflows/backend/test-backend.yml deleted file mode 100644 index 26bdc7bc..00000000 --- a/.github/workflows/backend/test-backend.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Test Backend -on: - push: - branches: ['main', 'staging', 'prod'] - paths: - - 'backend/**' - pull_request: - branches: ['main', 'staging', 'prod'] - paths: - - 'backend/**' -jobs: - test-backend: - name: Test Backend - runs-on: ubuntu-latest - defaults: - run: - shell: bash - working-directory: backend - services: - postgres: - image: postgres:16 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_PORT: 5432 - POSTGRES_DB: postgres - options: >- - --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - ports: - - 5432:5432 - steps: - - uses: actions/checkout@v4 - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.23' - cache: true - - name: Install dependencies - run: go mod download - - name: Install Goose - run: go install github.com/pressly/goose/v3/cmd/goose@v3.22.1 - - name: Run migrations - run: goose -dir .sqlc/migrations postgres "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable" up - - name: Run tests - id: tests - continue-on-error: true - run: | - # run tests and capture output - go test -v -coverprofile=coverage.out ./... 2>&1 | tee test_output.txt - # store the exit code explicitly - echo "::set-output name=exit_code::${PIPESTATUS[0]}" - - name: Generate coverage report - run: go tool cover -html=coverage.out -o coverage.html - - name: Upload coverage report - if: ${{ !env.ACT && github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v4 - with: - name: coverage-report - path: coverage.html - - name: Comment PR - if: ${{ !env.ACT && github.event_name == 'pull_request' && always() }} - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: "const fs = require('fs');\n\n// Read test output\nconst testOutput = fs.readFileSync('test_output.txt', 'utf8');\n\n// Get coverage - look for the last coverage number in the output\nlet coverage = 'N/A';\nconst coverageMatches = testOutput.match(/coverage: (\\d+\\.\\d+)% of statements/g) || [];\nif (coverageMatches.length > 0) {\n const lastMatch = coverageMatches[coverageMatches.length - 1];\n coverage = lastMatch.match(/(\\d+\\.\\d+)%/)[1] + '%';\n}\n\n// Check if any tests failed\nconst hasFailed = testOutput.includes('FAIL') && !testOutput.includes('FAIL\\t[build failed]');\nconst testStatus = hasFailed ? 'failure' : 'success';\nconst color = testStatus === 'success' ? '✅' : '❌';\n\n// Parse test failures\nlet failureDetails = '';\nif (hasFailed) {\n const errorTraces = testOutput.match(/\\s+.*?_test\\.go:\\d+:[\\s\\S]*?Test:\\s+.*$/gm) || [];\n const failures = testOutput.match(/--- FAIL: .*?(?=(?:---|\\z))/gs) || [];\n \n failureDetails = `\n
\n ❌ Test Failures\n \n \\`\\`\\`\n ${failures.join('\\n')}\n \n Error Details:\n ${errorTraces.map(trace => trace.trim()).join('\\n')}\n \\`\\`\\`\n
\n `;\n}\n\nconst output = `### Test Results ${color}\n\n**Status**: ${testStatus}\n**Coverage**: ${coverage}\n**OS**: \\`${{ runner.os }}\\`\n\n${failureDetails}\n\n
\nTest Details\n\n* Triggered by: @${{ github.actor }}\n* Commit: ${{ github.sha }}\n* Branch: ${{ github.ref }}\n* Workflow: ${{ github.workflow }}\n
`;\n\n// Find existing comment\nconst { data: comments } = await github.rest.issues.listComments({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.issue.number,\n});\n\nconst botComment = comments.find(comment => \n comment.user.type === 'Bot' && \n comment.body.includes('### Test Results')\n);\n\nif (botComment) {\n // Update existing comment\n await github.rest.issues.updateComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n comment_id: botComment.id,\n body: output\n });\n} else {\n // Create new comment\n await github.rest.issues.createComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.issue.number,\n body: output\n });\n}\n" - - uses: actions/cache@v4 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- diff --git a/.github/workflows/backend/deploy-preview-backend.yml b/.github/workflows/deploy-preview-backend.yml similarity index 95% rename from .github/workflows/backend/deploy-preview-backend.yml rename to .github/workflows/deploy-preview-backend.yml index 62325e5f..63357692 100644 --- a/.github/workflows/backend/deploy-preview-backend.yml +++ b/.github/workflows/deploy-preview-backend.yml @@ -46,8 +46,9 @@ jobs: rm -rf repo git clone git@github.com:${{ github.repository }}.git repo cd repo + cd backend echo "Copy deploy preview script" - cp .github/scripts/deploy-preview.sh . + cp ../.github/scripts/deploy-preview.sh . echo "Running deploy-preview.sh" chmod +x deploy-preview.sh ./deploy-preview.sh diff --git a/.github/workflows/frontend/deploy-preview-frontend.yml b/.github/workflows/deploy-preview-frontend.yml similarity index 91% rename from .github/workflows/frontend/deploy-preview-frontend.yml rename to .github/workflows/deploy-preview-frontend.yml index 2d9cde84..7a2b52e4 100644 --- a/.github/workflows/frontend/deploy-preview-frontend.yml +++ b/.github/workflows/deploy-preview-frontend.yml @@ -37,15 +37,16 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies - run: pnpm install --frozen-lockfile + run: cd frontend && pnpm install --frozen-lockfile - name: Build - run: pnpm build + run: cd frontend && pnpm build - name: Deploy to server uses: appleboy/scp-action@master with: host: ${{ secrets.PREVIEW_SERVER_IP }} username: ${{ secrets.PREVIEW_USER }} key: ${{ secrets.PREVIEW_SERVER_SSH_KEY }} - source: "dist/" + source: "frontend/dist/" target: "${{ secrets.TARGET_DIR }}" strip_components: 1 + diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml new file mode 100644 index 00000000..041e93c6 --- /dev/null +++ b/.github/workflows/test-backend.yml @@ -0,0 +1,59 @@ +on: + push: + branches: ['main', 'staging', 'prod'] + paths: + - 'backend/**' + pull_request: + branches: ['main', 'staging', 'prod'] + paths: + - 'backend/**' +jobs: + test-backend: + name: Test Backend + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_PORT: 5432 + POSTGRES_DB: postgres + options: >- + --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + ports: + - 5432:5432 + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache: true + - name: Install dependencies + run: cd backend && go mod download + - name: Install Goose + run: cd backend && go install github.com/pressly/goose/v3/cmd/goose@v3.22.1 + - name: Run migrations + run: cd backend && goose -dir .sqlc/migrations postgres "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable" up + - name: Run tests + run: | + # run tests and capture output + cd backend + go test -v -coverprofile=coverage.out ./... + - name: Generate coverage report + if: ${{ !env.ACT && github.event_name == 'pull_request' && always() }} + run: cd backend && go tool cover -html=coverage.out -o coverage.html + - name: Upload coverage report + if: ${{ !env.ACT && github.event_name == 'pull_request' && always() }} + uses: actions/upload-artifact@v4 + with: + name: coverage-report-backend + path: backend/coverage.html + # - name: Comment PR + # if: ${{ !env.ACT && github.event_name == 'pull_request' && always() }} + # uses: actions/github-script@v7 + # with: + # github-token: ${{ secrets.GITHUB_TOKEN }} + # script: "const fs = require('fs');\n\n// Read test output\nconst testOutput = fs.readFileSync('backend/test_output.txt', 'utf8');\n\n// Get coverage - look for the last coverage number in the output\nlet coverage = 'N/A';\nconst coverageMatches = testOutput.match(/coverage: (\\d+\\.\\d+)% of statements/g) || [];\nif (coverageMatches.length > 0) {\n const lastMatch = coverageMatches[coverageMatches.length - 1];\n coverage = lastMatch.match(/(\\d+\\.\\d+)%/)[1] + '%';\n}\n\n// Check if any tests failed\nconst hasFailed = testOutput.includes('FAIL') && !testOutput.includes('FAIL\\t[build failed]');\nconst testStatus = hasFailed ? 'failure' : 'success';\nconst color = testStatus === 'success' ? '✅' : '❌';\n\n// Parse test failures\nlet failureDetails = '';\nif (hasFailed) {\n const errorTraces = testOutput.match(/\\s+.*?_test\\.go:\\d+:[\\s\\S]*?Test:\\s+.*$/gm) || [];\n const failures = testOutput.match(/--- FAIL: .*?(?=(?:---|\\z))/gs) || [];\n \n failureDetails = `\n
\n ❌ Test Failures\n \n \\`\\`\\`\n ${failures.join('\\n')}\n \n Error Details:\n ${errorTraces.map(trace => trace.trim()).join('\\n')}\n \\`\\`\\`\n
\n `;\n}\n\nconst output = `### Test Results ${color}\n\n**Status**: ${testStatus}\n**Coverage**: ${coverage}\n**OS**: \\`${{ runner.os }}\\`\n\n${failureDetails}\n\n
\nTest Details\n\n* Triggered by: @${{ github.actor }}\n* Commit: ${{ github.sha }}\n* Branch: ${{ github.ref }}\n* Workflow: ${{ github.workflow }}\n
`;\n\n// Find existing comment\nconst { data: comments } = await github.rest.issues.listComments({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.issue.number,\n});\n\nconst botComment = comments.find(comment => \n comment.user.type === 'Bot' && \n comment.body.includes('### Test Results')\n);\n\nif (botComment) {\n // Update existing comment\n await github.rest.issues.updateComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n comment_id: botComment.id,\n body: output\n });\n} else {\n // Create new comment\n await github.rest.issues.createComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.issue.number,\n body: output\n });\n}\n" diff --git a/.github/workflows/frontend/test-frontend.yml b/.github/workflows/test-frontend.yml similarity index 65% rename from .github/workflows/frontend/test-frontend.yml rename to .github/workflows/test-frontend.yml index 05841915..95e46357 100644 --- a/.github/workflows/frontend/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -1,4 +1,3 @@ -name: Test Frontend on: push: branches: ['main', 'staging', 'prod'] @@ -10,13 +9,11 @@ on: - 'frontend/**' jobs: test-frontend: + name: Test Frontend runs-on: ubuntu-latest - defaults: - run: - shell: bash - working-directory: frontend steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v4 with: @@ -26,16 +23,14 @@ jobs: uses: actions/setup-node@v4 with: node-version: '20' - cache: 'pnpm' - name: Install dependencies - run: pnpm install --frozen-lockfile + run: cd frontend && pnpm install --frozen-lockfile - name: Run tests - run: pnpm test:ci + run: cd frontend && pnpm test:ci - name: Generate coverage report - run: pnpm coverage + run: cd frontend && pnpm coverage - name: Upload coverage report uses: actions/upload-artifact@v4 with: - name: coverage-report - path: coverage/ - retention-days: 7 + name: coverage-report-frontend + path: frontend/coverage/ diff --git a/backend/.gitignore b/backend/.gitignore index 164b1434..af6d1cfa 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -4,4 +4,7 @@ tmp *.pem -static \ No newline at end of file +static + +# ignore test coverage +coverage.out diff --git a/backend/Makefile b/backend/Makefile index 3a8f7b35..8333cdbd 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -73,7 +73,7 @@ start-testdb: @goose -dir .sqlc/migrations postgres "$(TEST_DB_URL)" up run-tests: - @go test -v ./... + @go test -v -coverprofile=coverage.out ./... stop-testdb: @echo "Stopping test db..." diff --git a/backend/README.md b/backend/README.md index 86c0b78f..6482d96a 100644 --- a/backend/README.md +++ b/backend/README.md @@ -9,9 +9,9 @@ Make sure to download **VERSION 1.23** for best compatibility. ### Install Pre-requisite tools -- Air (auto-reload backend): go install github.com/air-verse/air@latest -- SQLc (generate type-safe code from SQL queries): go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest -- Goose (SQL migration management tool): go install github.com/pressly/goose/v3/cmd/goose@latest +- Air (auto-reload backend): go install github.com/air-verse/air@1.61.1 +- SQLc (generate type-safe code from SQL queries): go install github.com/sqlc-dev/sqlc/cmd/sqlc@1.27.0 +- Goose (SQL migration management tool): go install github.com/pressly/goose/v3/cmd/goose@3.22.1 - Make - Docker @@ -22,8 +22,8 @@ Make sure to download **VERSION 1.23** for best compatibility. 1. Create a new PostgreSQL instance using docker with `make init-dev-db` 2. Start PostgreSQL for development `make start-dev-db` - Check health of DB `make health-dev-db` -4. Run migrations when ready `make up` -5. Start development server `make dev` +3. Run migrations when ready `make up` +4. Start development server `make dev` > Use `make query "SELECT ... FROM ..."` for quick query on the terminal. > You should also checkout the other available commands in the Makefile. diff --git a/frontend/index.html b/frontend/index.html index e4b78eae..f07f8f4d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - Vite + React + TS + SPUR