Skip to content

Commit 3fe752e

Browse files
commit green score tool
0 parents  commit 3fe752e

192 files changed

Lines changed: 20068 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/pull_request_template.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Green API Review Checklist (API Green Score)
2+
3+
Merci d’indiquer les **mesures avant/après** (payload/latence).
4+
5+
## Data Exchange
6+
- [ ] **DE11 – Pagination** (size borné ≤100, validé)
7+
- [ ] **DE08 – Filtrage** via `fields=` (whitelist, champs coûteux off par défaut)
8+
- [ ] **DE01/USXX – Format & compression** (JSON/CBOR, Gzip activé & testé)
9+
- [ ] **DE02/DE03 – Cache HTTP** (`Cache-Control` + **ETag**/`Last-Modified`**304** en test)
10+
- [ ] **DE06/US04 – Delta** (`/changes?since=…` ou `sinceVersion`)
11+
- [ ] **206 Partial Content** (`Range: bytes=` pour ressources volumineuses)
12+
13+
## Usage / Archi / Logs
14+
- [ ] **US01 – Query params** pour GET (page/size/fields)
15+
- [ ] **US07 – Monitoring erreurs** (Actuator/metrics)
16+
- [ ] **AR02 – Efficacité** (batch, agrégation, keyset pagination)
17+
- [ ] **LO01 – Logs utiles** (bytes, timeMs, 304) & rétention documentée
18+
- [ ] **Rate limiting** pour éviter rafales inutiles
19+
20+
## Sécurité & robustesse
21+
- [ ] Validation inputs (bornes `size`, whitelist `fields`)
22+
- [ ] Idempotency‑Key sur POST critiques
23+
24+
## Mesures
25+
```
26+
curl -s -w 'size=%{size_download} time=%{time_total}
27+
' -o /dev/null <URL>
28+
```
29+
- Avant : …
30+
- Après : …
31+
- Gain : … %
32+
33+
## 🌿 Green Score (automatique)
34+
> Le CI calculera automatiquement le **Green Score /100** et postera un commentaire détaillé sur cette PR.
35+
> Consultez le dashboard : `dashboard/index.html`
36+

.github/workflows/pr-green-api.yml

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
name: "🌿 Green API Score CI"
2+
3+
on:
4+
push:
5+
branches: [main, master, develop, feat/devoxx]
6+
pull_request:
7+
branches: [main, master]
8+
9+
permissions:
10+
contents: read
11+
pull-requests: write
12+
13+
concurrency:
14+
group: pr-green-api-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
env:
18+
JAVA_VERSION: '17'
19+
20+
jobs:
21+
build:
22+
name: "🔨 Build"
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v4
26+
- name: Setup JDK ${{ env.JAVA_VERSION }}
27+
uses: actions/setup-java@v4
28+
with:
29+
distribution: temurin
30+
java-version: ${{ env.JAVA_VERSION }}
31+
cache: maven
32+
- name: Build baseline
33+
run: mvn -q -DskipTests -f green-api-baseline/pom.xml package
34+
- name: Build optimized
35+
run: mvn -q -DskipTests -f green-api-optimized/pom.xml package
36+
- name: Upload jars
37+
uses: actions/upload-artifact@v4
38+
with:
39+
name: jars
40+
path: |
41+
green-api-baseline/target/*.jar
42+
green-api-optimized/target/*.jar
43+
44+
green-score:
45+
name: "🌿 Green Score Analysis"
46+
needs: build
47+
runs-on: ubuntu-latest
48+
steps:
49+
- uses: actions/checkout@v4
50+
- name: Setup JDK ${{ env.JAVA_VERSION }}
51+
uses: actions/setup-java@v4
52+
with:
53+
distribution: temurin
54+
java-version: ${{ env.JAVA_VERSION }}
55+
- name: Download jars
56+
uses: actions/download-artifact@v4
57+
with:
58+
name: jars
59+
- name: Start baseline (8080)
60+
run: |
61+
nohup java -jar green-api-baseline/target/green-api-baseline-0.0.1-SNAPSHOT.jar > baseline.log 2>&1 &
62+
echo $! > baseline.pid
63+
- name: Start optimized (8081)
64+
run: |
65+
nohup java -jar green-api-optimized/target/green-api-optimized-0.0.1-SNAPSHOT.jar > optimized.log 2>&1 &
66+
echo $! > optimized.pid
67+
- name: Wait for services
68+
run: |
69+
for i in $(seq 1 60); do
70+
if curl -sSf http://localhost:8080/actuator/health >/dev/null 2>&1 && \
71+
curl -sSf http://localhost:8081/actuator/health >/dev/null 2>&1; then
72+
echo "✅ Both services are up"; break
73+
fi
74+
sleep 2
75+
done
76+
- name: Run Green Score Analyzer
77+
run: |
78+
chmod +x scripts/green-score-analyzer.sh
79+
chmod +x scripts/green-score-analyzer_withdiscovery.sh
80+
BASELINE_PORT=8080 OPTIMIZED_PORT=8081 bash scripts/green-score-analyzer_withdiscovery.sh
81+
82+
- name: Generate Green Score badge
83+
run: |
84+
chmod +x scripts/generate-badge.sh
85+
bash scripts/generate-badge.sh reports/latest-report.json badges/green-score.svg || true
86+
87+
- name: Assertions — payload & 304 & Green Score
88+
shell: bash
89+
run: |
90+
set -e
91+
REPORT="reports/latest-report.json"
92+
echo "=== Green Score Report ==="
93+
python3 -m json.tool "$REPORT"
94+
95+
TOTAL=$(python3 -c "import json;print(json.load(open('$REPORT'))['green_score']['total'])")
96+
GRADE=$(python3 -c "import json;print(json.load(open('$REPORT'))['green_score']['grade'])")
97+
echo "## 🌿 Green Score: ${TOTAL}/100 (Grade: ${GRADE})" >> $GITHUB_STEP_SUMMARY
98+
99+
# Assert pagination < baseline/10
100+
B_SIZE=$(python3 -c "import json;r=json.load(open('$REPORT'));print(r['measurements']['baseline']['full_payload']['size_download'])")
101+
O_SIZE=$(python3 -c "import json;r=json.load(open('$REPORT'));print(r['measurements']['optimized']['pagination']['size_download'])")
102+
echo "Baseline: ${B_SIZE} bytes | Optimized paginated: ${O_SIZE} bytes" >> $GITHUB_STEP_SUMMARY
103+
python3 -c "b=$B_SIZE; o=$O_SIZE; assert o < b / 10, f'FAIL: optimized ({o}) >= baseline/10 ({b/10})'; print('✅ Pagination assertion PASSED')"
104+
105+
# Assert 304
106+
ETAG_CODE=$(python3 -c "import json;r=json.load(open('$REPORT'));print(r['measurements']['optimized']['etag_304']['http_code'])")
107+
python3 -c "assert $ETAG_CODE == 304, f'FAIL: expected 304, got $ETAG_CODE'; print('✅ ETag 304 assertion PASSED')"
108+
109+
# Assert Green Score >= 50
110+
python3 -c "assert $TOTAL >= 50, f'FAIL: Green Score {$TOTAL} < 50'; print('✅ Green Score >= 50 PASSED')"
111+
112+
# Regression gate (optional): green-score-threshold.json
113+
if [ -f green-score-threshold.json ]; then
114+
MIN_SCORE=$(python3 -c "import json;print(json.load(open('green-score-threshold.json'))['minScore'])")
115+
echo "Min score threshold: ${MIN_SCORE}" >> $GITHUB_STEP_SUMMARY
116+
python3 -c "total=$TOTAL; minimum=$MIN_SCORE; assert total >= minimum, f'FAIL: Green Score {total} < threshold {minimum}'; print('✅ Green Score threshold PASSED')"
117+
fi
118+
119+
- name: Upload report
120+
uses: actions/upload-artifact@v4
121+
if: always()
122+
with:
123+
name: green-score-report
124+
path: reports/
125+
126+
- name: Upload badge
127+
uses: actions/upload-artifact@v4
128+
if: always()
129+
with:
130+
name: green-score-badge
131+
path: badges/
132+
133+
- name: Comment PR with Green Score
134+
if: github.event_name == 'pull_request'
135+
uses: actions/github-script@v7
136+
with:
137+
script: |
138+
const fs = require('fs');
139+
const report = JSON.parse(fs.readFileSync('reports/latest-report.json', 'utf8'));
140+
const gs = report.green_score;
141+
const emoji = gs.total >= 80 ? '🟢' : gs.total >= 50 ? '🟡' : '🔴';
142+
143+
let body = `## ${emoji} Green API Score: **${gs.total}/100** (Grade: **${gs.grade}**)\n\n`;
144+
body += `| Règle | Score | Max |\n|---|---|---|\n`;
145+
146+
const labels = {
147+
'DE11_pagination': 'DE11 Pagination',
148+
'DE08_fields': 'DE08 Filtrage champs',
149+
'DE01_compression': 'DE01 Compression',
150+
'DE02_DE03_cache': 'DE02/03 Cache ETag',
151+
'DE06_delta': 'DE06 Delta',
152+
'range_206': '206 Range',
153+
'LO01_observability': 'LO01 Observabilité',
154+
'US07_rate_limit': 'US07 Rate Limit',
155+
'AR02_format_cbor': 'AR02 CBOR',
156+
};
157+
const maxScores = {
158+
DE11_pagination: 15, DE08_fields: 15, DE01_compression: 15,
159+
DE02_DE03_cache: 15, DE06_delta: 10, range_206: 10,
160+
LO01_observability: 5, US07_rate_limit: 5, AR02_format_cbor: 10
161+
};
162+
163+
for (const [key, label] of Object.entries(labels)) {
164+
const score = gs.breakdown[key] || 0;
165+
const max = maxScores[key] || 0;
166+
const icon = score >= max * 0.75 ? '✅' : score >= max * 0.4 ? '⚠️' : '❌';
167+
body += `| ${icon} ${label} | ${score} | ${max} |\n`;
168+
}
169+
body += `\n📅 *${report.timestamp}*`;
170+
171+
github.rest.issues.createComment({
172+
issue_number: context.issue.number,
173+
owner: context.repo.owner,
174+
repo: context.repo.repo,
175+
body: body
176+
});
177+
178+
- name: Upload logs (on failure)
179+
if: failure()
180+
uses: actions/upload-artifact@v4
181+
with:
182+
name: server-logs
183+
path: |
184+
baseline.log
185+
optimized.log
186+
187+
- name: Stop services
188+
if: always()
189+
run: |
190+
kill $(cat baseline.pid) 2>/dev/null || true
191+
kill $(cat optimized.pid) 2>/dev/null || true
192+
193+
spectral-lint:
194+
name: "🔬 Spectral OpenAPI Lint"
195+
needs: build
196+
runs-on: ubuntu-latest
197+
steps:
198+
- uses: actions/checkout@v4
199+
- name: Setup JDK ${{ env.JAVA_VERSION }}
200+
uses: actions/setup-java@v4
201+
with:
202+
distribution: temurin
203+
java-version: ${{ env.JAVA_VERSION }}
204+
- name: Download jars
205+
uses: actions/download-artifact@v4
206+
with:
207+
name: jars
208+
- name: Start optimized
209+
run: |
210+
nohup java -jar green-api-optimized/target/green-api-optimized-0.0.1-SNAPSHOT.jar > opt.log 2>&1 &
211+
for i in $(seq 1 60); do
212+
curl -sSf http://localhost:8081/actuator/health >/dev/null 2>&1 && break
213+
sleep 2
214+
done
215+
- name: Fetch OpenAPI spec
216+
run: curl -s http://localhost:8081/v3/api-docs -o openapi.json
217+
- name: Install Spectral
218+
run: npm install -g @stoplight/spectral-cli
219+
- name: Run Spectral with Green API rules
220+
run: spectral lint openapi.json --ruleset .spectral.yml --format stylish || true
221+
222+
auto-discover:
223+
name: "🔍 Auto-Discover Analysis"
224+
needs: build
225+
runs-on: ubuntu-latest
226+
steps:
227+
- uses: actions/checkout@v4
228+
- name: Setup JDK ${{ env.JAVA_VERSION }}
229+
uses: actions/setup-java@v4
230+
with:
231+
distribution: temurin
232+
java-version: ${{ env.JAVA_VERSION }}
233+
- name: Setup Python
234+
uses: actions/setup-python@v5
235+
with:
236+
python-version: '3.12'
237+
- name: Install Spectral
238+
run: npm install -g @stoplight/spectral-cli
239+
- name: Download jars
240+
uses: actions/download-artifact@v4
241+
with:
242+
name: jars
243+
- name: Start optimized API
244+
run: |
245+
nohup java -jar green-api-optimized/target/green-api-optimized-0.0.1-SNAPSHOT.jar > optimized.log 2>&1 &
246+
echo $! > optimized.pid
247+
for i in $(seq 1 60); do
248+
curl -sSf http://localhost:8081/actuator/health >/dev/null 2>&1 && echo "API ready" && break
249+
sleep 2
250+
done
251+
- name: Run Auto-Discover Analyzer
252+
run: |
253+
python3 scripts/green-api-auto-discover.py \
254+
--target http://localhost:8081 \
255+
--repeat 3
256+
- name: Upload reports
257+
uses: actions/upload-artifact@v4
258+
if: always()
259+
with:
260+
name: auto-discover-report
261+
path: reports/
262+
- name: Stop services
263+
if: always()
264+
run: kill $(cat optimized.pid) 2>/dev/null || true
265+

.spectral.yml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
extends: [[spectral:oas, recommended]]
2+
3+
rules:
4+
# ═══════════════════════════════════════════════════════════
5+
# 🌿 Green API Score — Spectral Rules
6+
# Règles de linting OpenAPI pour l'éco-conception d'API
7+
# ═══════════════════════════════════════════════════════════
8+
9+
# DE11 — Pagination obligatoire sur les collections
10+
green-api-pagination:
11+
description: "DE11 — Les endpoints de collection GET doivent supporter la pagination (page/size ou limit/offset)"
12+
severity: warn
13+
given: "$.paths[*].get"
14+
then:
15+
- field: parameters
16+
function: truthy
17+
message: "{{description}} — Ajoutez des paramètres page/size sur les GET de collection."
18+
19+
# DE08 — Filtrage de champs
20+
green-api-fields-filter:
21+
description: "DE08 — Les endpoints GET devraient supporter un paramètre 'fields' pour le filtrage de champs"
22+
severity: info
23+
given: "$.paths[*].get.parameters[*]"
24+
then:
25+
field: name
26+
function: pattern
27+
functionOptions:
28+
notMatch: "^$"
29+
message: "Pensez à ajouter un paramètre 'fields' pour réduire le payload (DE08)."
30+
31+
# DE01 — Format de réponse
32+
green-api-response-format:
33+
description: "DE01 — Les réponses doivent utiliser application/json (ou application/cbor)"
34+
severity: warn
35+
given: "$.paths[*][*].responses[*].content"
36+
then:
37+
field: "application/json"
38+
function: truthy
39+
message: "{{description}}"
40+
41+
# DE02/DE03 — Cache headers
42+
green-api-cache-headers:
43+
description: "DE02/DE03 — Les réponses 200 devraient documenter les headers de cache (ETag, Cache-Control)"
44+
severity: info
45+
given: "$.paths[*].get.responses.200.headers"
46+
then:
47+
function: truthy
48+
message: "Documentez les headers ETag et Cache-Control dans vos réponses 200 (DE02/DE03)."
49+
50+
# Pas de descriptions vides
51+
green-api-operation-description:
52+
description: "Chaque opération doit avoir une description"
53+
severity: warn
54+
given: "$.paths[*][get,post,put,patch,delete]"
55+
then:
56+
field: description
57+
function: truthy
58+
message: "Ajoutez une description à cette opération pour la documentation."
59+
60+
# Réponses d'erreur documentées
61+
green-api-error-responses:
62+
description: "US07 — Documentez les réponses d'erreur (400, 404, 500)"
63+
severity: info
64+
given: "$.paths[*][*].responses"
65+
then:
66+
field: "404"
67+
function: truthy
68+
message: "Documentez la réponse 404 pour améliorer l'observabilité (US07)."
69+
70+
# Pas de payload excessif dans les exemples
71+
oas3-valid-media-example:
72+
severity: warn
73+
74+
# Info obligatoire
75+
info-contact:
76+
severity: warn
77+
info-description:
78+
severity: warn
79+

0 commit comments

Comments
 (0)