|
| 1 | +#!/usr/bin/env bash |
| 2 | +# check-bdd.sh — feature file existence + cross-language parity via alias map. |
| 3 | +set -euo pipefail |
| 4 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 5 | +source "$SCRIPT_DIR/lib/json-emit.sh" |
| 6 | + |
| 7 | +LANGUAGE="${LANGUAGE:-python}" |
| 8 | +REPO_ROOT="${REPO_ROOT:-.}" |
| 9 | +DIFF_FILE="${DIFF_FILE:-/dev/stdin}" |
| 10 | +SDK_SIBLING_PATH="${SDK_SIBLING_PATH:-}" |
| 11 | +CONFIG_DIR="${CONFIG_DIR:-$REPO_ROOT/.claude/config}" |
| 12 | + |
| 13 | +STARTED=$(now_iso) |
| 14 | +findings=$(mktemp); trap 'rm -f "$findings"' EXIT |
| 15 | + |
| 16 | +diff_content=$(cat "$DIFF_FILE" 2>/dev/null || echo "") |
| 17 | + |
| 18 | +# Extract new/modified modules from diff |
| 19 | +if [ "$LANGUAGE" = "python" ]; then |
| 20 | + modules=$(echo "$diff_content" | grep -oE 'src/sap_cloud_sdk/[a-z_]+/' 2>/dev/null | sed 's|src/sap_cloud_sdk/||; s|/$||' | grep -v '^core$' | sort -u || true) |
| 21 | +else |
| 22 | + modules=$(echo "$diff_content" | grep -oE 'src/main/java/com/sap/cloud/sdk/[a-z_]+/' 2>/dev/null | sed 's|src/main/java/com/sap/cloud/sdk/||; s|/$||' | grep -v '^core$' | sort -u || true) |
| 23 | +fi |
| 24 | + |
| 25 | +# resolve alias mapping (python name → java name and vice versa) |
| 26 | +# The YAML is a simple list of pairs: |
| 27 | +# aliases: |
| 28 | +# - python: dms |
| 29 | +# java: documentmanagement |
| 30 | +# awk on `$NF` (last field) extracts the value cleanly even when the key |
| 31 | +# column varies. We only pair a python: line with the very next java: line. |
| 32 | +resolve_sibling_name() { |
| 33 | + local mod="$1" dir="$LANGUAGE" |
| 34 | + local aliases="$CONFIG_DIR/module-aliases.yaml" |
| 35 | + if [ ! -f "$aliases" ]; then echo "$mod"; return; fi |
| 36 | + if [ "$dir" = "python" ]; then |
| 37 | + awk -v mod="$mod" ' |
| 38 | + /^[[:space:]]*-?[[:space:]]*python:[[:space:]]*/ { |
| 39 | + sub(/^[^:]*:[[:space:]]*/, ""); p_name=$0; next |
| 40 | + } |
| 41 | + /^[[:space:]]*java:[[:space:]]*/ { |
| 42 | + sub(/^[^:]*:[[:space:]]*/, ""); j_name=$0 |
| 43 | + if (p_name == mod) { print j_name; exit } |
| 44 | + p_name="" |
| 45 | + } |
| 46 | + ' "$aliases" |
| 47 | + return |
| 48 | + else |
| 49 | + awk -v mod="$mod" ' |
| 50 | + /^[[:space:]]*-?[[:space:]]*python:[[:space:]]*/ { |
| 51 | + sub(/^[^:]*:[[:space:]]*/, ""); p_name=$0; next |
| 52 | + } |
| 53 | + /^[[:space:]]*java:[[:space:]]*/ { |
| 54 | + sub(/^[^:]*:[[:space:]]*/, ""); j_name=$0 |
| 55 | + if (j_name == mod) { print p_name; exit } |
| 56 | + p_name="" |
| 57 | + } |
| 58 | + ' "$aliases" |
| 59 | + return |
| 60 | + fi |
| 61 | +} |
| 62 | + |
| 63 | +while IFS= read -r mod; do |
| 64 | + [ -z "$mod" ] && continue |
| 65 | + |
| 66 | + # Path for THIS repo's feature file |
| 67 | + # FP-C-01: BDD-01 must accept ANY .feature file under the module's integration |
| 68 | + # dir, not just `<module>.feature`. |
| 69 | + if [ "$LANGUAGE" = "python" ]; then |
| 70 | + feature_dir="$REPO_ROOT/tests/$mod/integration" |
| 71 | + feature_path="$feature_dir/$mod.feature" # kept for BDD-02 sibling comparison |
| 72 | + mod_dir="$REPO_ROOT/src/sap_cloud_sdk/$mod" |
| 73 | + else |
| 74 | + feature_dir="$REPO_ROOT/src/test/resources/com/sap/applicationfoundation/$mod/integration" |
| 75 | + feature_path="$feature_dir/$mod.feature" # kept for BDD-02 sibling comparison |
| 76 | + mod_dir="$REPO_ROOT/src/main/java/com/sap/cloud/sdk/$mod" |
| 77 | + fi |
| 78 | + |
| 79 | + # BDD-01: at least one .feature file exists in the module's integration dir |
| 80 | + # (only fire if module has any source files and PR creates the module). |
| 81 | + has_any_feature=false |
| 82 | + if [ -d "$feature_dir" ]; then |
| 83 | + if compgen -G "$feature_dir/*.feature" > /dev/null 2>&1; then |
| 84 | + has_any_feature=true |
| 85 | + fi |
| 86 | + fi |
| 87 | + |
| 88 | + if [ "$has_any_feature" = "false" ] && [ -d "$mod_dir" ]; then |
| 89 | + # Detect if this PR creates any new file INSIDE the module. |
| 90 | + # `new file mode` is on its own line before the `+++ b/<path>` header, |
| 91 | + # so we scan block-by-block to link them. |
| 92 | + is_new_module=$(echo "$diff_content" | awk -v mod="$mod" -v lang="$LANGUAGE" ' |
| 93 | + BEGIN { |
| 94 | + if (lang == "python") pat = "src/sap_cloud_sdk/" mod "/" |
| 95 | + else pat = "src/main/java/com/sap/cloud/sdk/" mod "/" |
| 96 | + } |
| 97 | + /^diff --git/ { is_new = 0; next } |
| 98 | + /^new file mode/ { is_new = 1; next } |
| 99 | + /^\+\+\+ b\// { |
| 100 | + if (is_new && index($0, pat) > 0) { print "true"; exit } |
| 101 | + } |
| 102 | + ') |
| 103 | + if [ "$is_new_module" = "true" ]; then |
| 104 | + emit_finding "BDD-01" "BLOCK" "tests/$mod/integration/" 1 \ |
| 105 | + "New module '$mod' has no .feature files under integration/" \ |
| 106 | + "Create at least one $feature_dir/*.feature with cross-language-consistent scenarios" >> "$findings" |
| 107 | + fi |
| 108 | + fi |
| 109 | + |
| 110 | + # BDD-02: sibling repo parity |
| 111 | + sibling_name=$(resolve_sibling_name "$mod") |
| 112 | + [ -z "$sibling_name" ] && sibling_name="$mod" |
| 113 | + |
| 114 | + if [ -n "$SDK_SIBLING_PATH" ] && [ -d "$SDK_SIBLING_PATH" ]; then |
| 115 | + if [ "$LANGUAGE" = "python" ]; then |
| 116 | + # Python repo — sibling is Java |
| 117 | + sibling_feature="$SDK_SIBLING_PATH/src/test/resources/com/sap/applicationfoundation/$sibling_name/integration/$sibling_name.feature" |
| 118 | + sibling_module_dir="$SDK_SIBLING_PATH/src/main/java/com/sap/cloud/sdk/$sibling_name" |
| 119 | + else |
| 120 | + sibling_feature="$SDK_SIBLING_PATH/tests/$sibling_name/integration/$sibling_name.feature" |
| 121 | + sibling_module_dir="$SDK_SIBLING_PATH/src/sap_cloud_sdk/$sibling_name" |
| 122 | + fi |
| 123 | + # If the sibling module dir exists, and sibling has no feature but we do (or vice versa) |
| 124 | + if [ -d "$sibling_module_dir" ] && [ ! -f "$sibling_feature" ] && [ -f "$feature_path" ]; then |
| 125 | + emit_finding "BDD-02" "FLAG" "$feature_path" 1 \ |
| 126 | + "Sibling SDK ($sibling_name) has module but no BDD feature — parity broken" "" >> "$findings" |
| 127 | + fi |
| 128 | + if [ -d "$sibling_module_dir" ] && [ -f "$sibling_feature" ] && [ ! -f "$feature_path" ] && [ -d "$mod_dir" ]; then |
| 129 | + emit_finding "BDD-02" "BLOCK" "tests/.../$mod.feature" 1 \ |
| 130 | + "Module '$mod' exists in sibling SDK ($sibling_name) with BDD feature — this repo must have equivalent" "" >> "$findings" |
| 131 | + fi |
| 132 | + fi |
| 133 | +done <<< "$modules" |
| 134 | + |
| 135 | +status=$(status_from_findings < "$findings") |
| 136 | +emit_report "bdd" "$LANGUAGE" "$status" "$STARTED" < "$findings" |
0 commit comments