1+ name : Test Suite
2+
3+ on :
4+ pull_request :
5+ branches : [main]
6+ workflow_dispatch :
7+
8+ jobs :
9+ test :
10+ runs-on : ubuntu-latest
11+ timeout-minutes : 15
12+
13+ services :
14+ mysql :
15+ image : mysql:8
16+ env :
17+ MYSQL_ROOT_PASSWORD : password
18+ MYSQL_DATABASE : catalog_test
19+ ports :
20+ - 3307:3306
21+ options : >-
22+ --health-cmd="mysqladmin ping"
23+ --health-interval=10s
24+ --health-timeout=5s
25+ --health-retries=3
26+
27+ opensearch :
28+ image : opensearchproject/opensearch:3
29+ env :
30+ cluster.name : test-opensearch-cluster
31+ node.name : test-opensearch
32+ discovery.type : single-node
33+ bootstrap.memory_lock : true
34+ OPENSEARCH_JAVA_OPTS : -Xms256m -Xmx256m
35+ DISABLE_SECURITY_PLUGIN : true
36+ ports :
37+ - 9201:9200
38+ options : >-
39+ --health-cmd="curl -f http://localhost:9200/_cluster/health || exit 1"
40+ --health-interval=30s
41+ --health-timeout=10s
42+ --health-retries=3
43+
44+ steps :
45+ - name : Checkout code
46+ uses : actions/checkout@v4
47+
48+ - name : Setup pnpm
49+ uses : pnpm/action-setup@v4
50+
51+ - name : Setup Node.js
52+ uses : actions/setup-node@v4
53+ with :
54+ node-version : ' lts/*'
55+ cache : pnpm
56+
57+ - name : Install dependencies
58+ run : pnpm install --frozen-lockfile
59+
60+ - name : Generate Prisma client
61+ run : pnpm run generate
62+
63+ - name : Wait for services to be ready
64+ run : |
65+ # Wait for MySQL
66+ for i in {1..30}; do
67+ if mysqladmin ping -h 127.0.0.1 -P 3307 -u root -ppassword &> /dev/null; then
68+ echo "MySQL is ready"
69+ break
70+ fi
71+ echo "Waiting for MySQL..."
72+ sleep 2
73+ done
74+
75+ # Wait for OpenSearch
76+ for i in {1..30}; do
77+ if curl -f http://localhost:9201/_cluster/health &> /dev/null; then
78+ echo "OpenSearch is ready"
79+ break
80+ fi
81+ echo "Waiting for OpenSearch..."
82+ sleep 2
83+ done
84+
85+ - name : Run database migrations
86+ run : pnpm run db:migrate
87+ env :
88+ DATABASE_URL : mysql://root:password@localhost:3307/catalog_test
89+
90+ - name : Run linting
91+ run : |
92+ pnpm run lint:biome
93+ pnpm run lint:types
94+
95+ - name : Run unit tests
96+ run : pnpm run test
97+ env :
98+ DATABASE_URL : mysql://root:password@localhost:3307/catalog_test
99+ OPENSEARCH_URL : http://localhost:9201
100+ NODE_ENV : test
101+
102+ - name : Run tests with coverage
103+ run : pnpm run test:coverage
104+ env :
105+ DATABASE_URL : mysql://root:password@localhost:3307/catalog_test
106+ OPENSEARCH_URL : http://localhost:9201
107+ NODE_ENV : test
108+
109+ - name : Check coverage threshold
110+ run : |
111+ # Extract coverage percentage from coverage report
112+ COVERAGE=$(pnpm run test:coverage --reporter=json | jq -r '.total.lines.pct')
113+ echo "Coverage: $COVERAGE%"
114+
115+ # Fail if coverage is below 95%
116+ if (( $(echo "$COVERAGE < 95" | bc -l) )); then
117+ echo "Coverage $COVERAGE% is below required 95%"
118+ exit 1
119+ fi
120+ echo "Coverage check passed: $COVERAGE%"
121+
122+ - name : Upload coverage reports
123+ uses : codecov/codecov-action@v4
124+ if : always()
125+ with :
126+ file : ./coverage/coverage-final.json
127+ fail_ci_if_error : false
128+
129+ - name : Comment PR with test results
130+ uses : actions/github-script@v7
131+ if : github.event_name == 'pull_request' && always()
132+ with :
133+ script : |
134+ const fs = require('fs');
135+
136+ // Read coverage report if it exists
137+ let coverageComment = '';
138+ try {
139+ const coverage = JSON.parse(fs.readFileSync('./coverage/coverage-final.json', 'utf8'));
140+ const total = coverage.total;
141+ coverageComment = `
142+ ## 📊 Test Coverage
143+
144+ | Metric | Percentage | Status |
145+ |--------|------------|--------|
146+ | Lines | ${total.lines.pct}% | ${total.lines.pct >= 95 ? '✅' : '❌'} |
147+ | Functions | ${total.functions.pct}% | ${total.functions.pct >= 95 ? '✅' : '❌'} |
148+ | Branches | ${total.branches.pct}% | ${total.branches.pct >= 95 ? '✅' : '❌'} |
149+ | Statements | ${total.statements.pct}% | ${total.statements.pct >= 95 ? '✅' : '❌'} |
150+
151+ **Minimum required coverage: 95%**
152+ `;
153+ } catch (error) {
154+ coverageComment = '⚠️ Coverage report not available';
155+ }
156+
157+ const comment = `
158+ ## 🧪 Test Results
159+
160+ The test suite has completed for this PR.
161+
162+ ${coverageComment}
163+
164+ ### ✅ Tests Included
165+ - Unit tests for routes and utilities
166+ - Integration tests with real database
167+ - OpenAPI schema validation
168+ - Error handling scenarios
169+
170+ Please review the test results above and ensure all checks are passing.
171+ `;
172+
173+ github.rest.issues.createComment({
174+ issue_number: context.issue.number,
175+ owner: context.repo.owner,
176+ repo: context.repo.repo,
177+ body: comment
178+ });
179+
180+ # Job to enforce that tests must pass
181+ test-status-check :
182+ runs-on : ubuntu-latest
183+ needs : test
184+ if : always()
185+ steps :
186+ - name : Test Status Check
187+ run : |
188+ if [ "${{ needs.test.result }}" != "success" ]; then
189+ echo "Tests failed or were cancelled"
190+ exit 1
191+ fi
192+ echo "All tests passed successfully"
0 commit comments