budaidev commented on code in PR #5515:
URL: https://github.com/apache/fineract/pull/5515#discussion_r2836611448


##########
.github/workflows/verify-api-backward-compatibility.yml:
##########
@@ -0,0 +1,205 @@
+name: Verify API Backward Compatibility
+
+on: [pull_request]
+
+permissions:
+  contents: read
+  pull-requests: write
+
+jobs:
+  api-compatibility-check:
+    runs-on: ubuntu-24.04
+    timeout-minutes: 15
+
+    env:
+      TZ: Asia/Kolkata
+
+    steps:
+      - name: Checkout base branch
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
+        with:
+          repository: ${{ github.event.pull_request.base.repo.full_name }}
+          ref: ${{ github.event.pull_request.base.ref }}
+          fetch-depth: 0
+          path: baseline
+
+      - name: Set up JDK 21
+        uses: actions/setup-java@v5
+        with:
+          distribution: 'zulu'
+          java-version: '21'
+
+      - name: Generate baseline spec
+        working-directory: baseline
+        run: ./gradlew :fineract-provider:resolve --no-daemon
+
+      - name: Checkout PR branch
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
+        with:
+          repository: ${{ github.event.pull_request.head.repo.full_name }}
+          ref: ${{ github.event.pull_request.head.sha }}
+          fetch-depth: 0
+          path: current
+
+      - name: Generate PR spec
+        working-directory: current
+        run: ./gradlew :fineract-provider:resolve --no-daemon
+
+      - name: Sanitize specs
+        run: |
+          python3 -c "
+          import json, sys
+
+          def sanitize(path):
+              with open(path) as f:
+                  spec = json.load(f)
+              fixed = 0
+              for path_item in spec.get('paths', {}).values():
+                  for op in path_item.values():
+                      if not isinstance(op, dict) or 'requestBody' not in op:
+                          continue
+                      for media in op['requestBody'].get('content', 
{}).values():
+                          if 'schema' not in media:
+                              media['schema'] = {'type': 'object'}
+                              fixed += 1
+              if fixed:
+                  with open(path, 'w') as f:
+                      json.dump(spec, f)
+              print(f'{path}: fixed {fixed} entries')
+
+          
sanitize('${GITHUB_WORKSPACE}/baseline/fineract-provider/build/resources/main/static/fineract.json')
+          
sanitize('${GITHUB_WORKSPACE}/current/fineract-provider/build/resources/main/static/fineract.json')
+          "
+
+      - name: Check breaking changes
+        id: breaking-check
+        continue-on-error: true
+        working-directory: current
+        run: |
+          ./gradlew :fineract-provider:checkBreakingChanges \
+            
-PapiBaseline="${GITHUB_WORKSPACE}/baseline/fineract-provider/build/resources/main/static/fineract.json"
 \
+            --no-daemon --quiet > /dev/null 2>&1
+
+      - name: Build report
+        if: steps.breaking-check.outcome == 'failure'
+        id: report
+        run: |
+          REPORT_DIR="current/fineract-provider/build/swagger-brake"
+
+          python3 -c "
+          import json, glob, os
+          from collections import defaultdict
+
+          RULE_DESC = {
+              'R001': 'Standard API changed to beta',
+              'R002': 'Path deleted',
+              'R003': 'Request media type deleted',
+              'R004': 'Request parameter deleted',
+              'R005': 'Request parameter enum value deleted',
+              'R006': 'Request parameter location changed',
+              'R007': 'Request parameter made required',
+              'R008': 'Request parameter type changed',
+              'R009': 'Request attribute removed',
+              'R010': 'Request type changed',
+              'R011': 'Request enum value deleted',
+              'R012': 'Response code deleted',
+              'R013': 'Response media type deleted',
+              'R014': 'Response attribute removed',
+              'R015': 'Response type changed',
+              'R016': 'Response enum value deleted',
+              'R017': 'Request parameter constraint changed',
+          }
+
+          report_dir = '${REPORT_DIR}'
+          files = sorted(glob.glob(os.path.join(report_dir, '*.json')))
+          if not files:
+              body = 'Breaking change detected but no report file found.'
+          else:
+              with open(files[0]) as f:
+                  data = json.load(f)
+
+              all_changes = []
+              for items in data.get('breakingChanges', {}).values():
+                  all_changes.extend(items)
+
+              if not all_changes:
+                  body = 'Breaking change detected but no details available in 
report.'
+              else:
+                  def detail(c):
+                      for key in ('attributeName', 'attribute', 'name', 
'mediaType', 'enumValue', 'code'):
+                          v = c.get(key)
+                          if v:
+                              val = v.rsplit('.', 1)[-1]
+                              if key in ('attributeName', 'attribute', 'name'):
+                                  return val
+                              return f'{key}={val}'
+                      return '-'
+
+                  groups = defaultdict(list)
+                  for c in all_changes:
+                      groups[(c.get('ruleCode', '?'), detail(c))].append(c)
+
+                  lines = []
+                  lines.append('| Rule | Description | Detail | Affected 
endpoints | Count |')
+                  
lines.append('|------|-------------|--------|--------------------|-------|')
+                  for (rule, det), items in sorted(groups.items()):
+                      desc = RULE_DESC.get(rule, '')
+                      eps = sorted(set(
+                          f'{c.get(\"method\",\"\")} {c.get(\"path\",\"\")}'
+                          for c in items if c.get('path')
+                      ))
+                      ep_str = ', '.join(f'\`{e}\`' for e in eps[:5])
+                      if len(eps) > 5:
+                          ep_str += f' +{len(eps)-5} more'
+                      lines.append(f'| {rule} | {desc} | \`{det}\` | {ep_str} 
| {len(items)} |')
+
+                  lines.append('')
+                  lines.append(f'**Total: {len(all_changes)} violations across 
{len(groups)} unique changes**')
+                  body = '\n'.join(lines)
+
+          with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
+              f.write('has_report=true\n')
+
+          report_file = '${GITHUB_WORKSPACE}/breaking-changes-report.md'
+          with open(report_file, 'w') as f:
+              f.write('## Breaking API Changes Detected\n\n')
+              f.write(body)
+              f.write('\n\n> **Note:** This check is informational only and 
does not block the PR.\n')
+
+          # Also write to step summary
+          with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as f:
+              f.write('## Breaking API Changes Detected\n\n')
+              f.write(body)
+              f.write('\n\n> **Note:** This check is informational only and 
does not block the PR.\n')

Review Comment:
   You can check it on the fork directly (now the develop is out sync, so it 
shows error):
   https://github.com/openMF/fineract/pull/191



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to