syntaxai/tdd.md · main · tools / sama-cli / src / b32_checks.test.sh

b32_checks.test.sh 199 lines · 6941 bytes raw
#!/usr/bin/env bash
# Sibling test for b32_checks.sh. Builds a fixture tree under a
# temp dir, runs each of the seven §4 checks against it, and
# asserts the expected verdict. Cross-validates the checks against
# the same kind of "synthetic small repo" the TS verifier tests in
# src/b32_sama_v2_verify.test.ts — same shapes, different language.

SAMA_SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
# shellcheck disable=SC1091
. "$SAMA_SRC_DIR/a31_constants.sh"
# shellcheck disable=SC1091
. "$SAMA_SRC_DIR/b32_utils.sh"
# shellcheck disable=SC1091
. "$SAMA_SRC_DIR/b32_checks.sh"

TESTS_RUN=0
TESTS_FAILED=0

assert_eq() {
  local expected="$1" actual="$2" label="$3"
  TESTS_RUN=$((TESTS_RUN + 1))
  if [ "$expected" = "$actual" ]; then
    printf "  ok  %s\n" "$label"
  else
    TESTS_FAILED=$((TESTS_FAILED + 1))
    printf "  FAIL %s\n    expected: %s\n    actual:   %s\n" "$label" "$expected" "$actual"
  fi
}

assert_contains_any() {
  local label="$1"
  shift
  local needle="$1"
  shift
  local found=0
  for item in "$@"; do
    case "$item" in
      *"$needle"*) found=1; break ;;
    esac
  done
  TESTS_RUN=$((TESTS_RUN + 1))
  if [ "$found" = "1" ]; then
    printf "  ok  %s\n" "$label"
  else
    TESTS_FAILED=$((TESTS_FAILED + 1))
    printf "  FAIL %s — needle \`%s\` not in any violation\n" "$label" "$needle"
  fi
}

TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT

setup_clean_repo() {
  rm -rf "$TMP_DIR/repo"
  mkdir -p "$TMP_DIR/repo/src"
  cat > "$TMP_DIR/repo/sama.profile.toml" <<'EOF'
sama_version = "2.0"
profile = "fixture"
extension = ".ts"

[layers.0]
prefixes = ["a_"]

[layers.1]
prefixes = ["b_"]

[layers.2]
prefixes = ["c_"]

[layers.3]
prefixes = ["d_"]
EOF
}

# — Check 1: Sorted (passing case) -------------------------------
setup_clean_repo
printf "export const x = 1;\n" > "$TMP_DIR/repo/src/a_pure.ts"
parse_profile "$TMP_DIR/repo/sama.profile.toml"
collect_input "$TMP_DIR/repo" "$TMP_DIR/repo/src"
run_check_sorted
assert_eq "0" "${#CHECK_VIOLATIONS[@]}" "Sorted: clean fixture has no violations"

# — Check 2: Architecture (unprefixed file fails) -----------------
setup_clean_repo
printf "export const x = 1;\n" > "$TMP_DIR/repo/src/orphan.ts"
parse_profile "$TMP_DIR/repo/sama.profile.toml"
collect_input "$TMP_DIR/repo" "$TMP_DIR/repo/src"
run_check_architecture
v_count="${#CHECK_VIOLATIONS[@]}"
assert_eq "1" "$v_count" "Architecture: unprefixed file flagged"
assert_contains_any "Architecture: detail mentions unprefixed" "unprefixed" "${CHECK_VIOLATIONS[@]}"

# — Check 3: Modeled (tests) (b_ file without sibling test) ------
setup_clean_repo
printf "export const f = 1;\n" > "$TMP_DIR/repo/src/b_core.ts"
parse_profile "$TMP_DIR/repo/sama.profile.toml"
collect_input "$TMP_DIR/repo" "$TMP_DIR/repo/src"
run_check_modeled_tests
assert_eq "1" "${#CHECK_VIOLATIONS[@]}" "Modeled (tests): b_ file without sibling test fails"

# Pass when sibling test exists.
setup_clean_repo
printf "export const f = 1;\n" > "$TMP_DIR/repo/src/b_core.ts"
printf "// test\n" > "$TMP_DIR/repo/src/b_core.test.ts"
parse_profile "$TMP_DIR/repo/sama.profile.toml"
collect_input "$TMP_DIR/repo" "$TMP_DIR/repo/src"
run_check_modeled_tests
assert_eq "0" "${#CHECK_VIOLATIONS[@]}" "Modeled (tests): sibling test satisfies"

# — Check 4: Modeled (boundary) (JSON.parse in Layer 1 fails) ----
setup_clean_repo
printf "export const f = (s) => JSON.parse(s);\n" > "$TMP_DIR/repo/src/b_naughty.ts"
parse_profile "$TMP_DIR/repo/sama.profile.toml"
collect_input "$TMP_DIR/repo" "$TMP_DIR/repo/src"
run_check_modeled_boundary
v_count="${#CHECK_VIOLATIONS[@]}"
assert_eq "1" "$v_count" "Modeled (boundary): JSON.parse in Layer 1 flagged"

# Layer 2 is the legitimate site — same content should not fire.
setup_clean_repo
printf "export const f = (s) => JSON.parse(s);\n" > "$TMP_DIR/repo/src/c_adapter.ts"
parse_profile "$TMP_DIR/repo/sama.profile.toml"
collect_input "$TMP_DIR/repo" "$TMP_DIR/repo/src"
run_check_modeled_boundary
assert_eq "0" "${#CHECK_VIOLATIONS[@]}" "Modeled (boundary): JSON.parse in Layer 2 is OK"

# String literal containing JSON.parse should NOT false-positive.
setup_clean_repo
printf 'const x = "JSON.parse(input)";\nexport const y = x.length;\n' > "$TMP_DIR/repo/src/b_safe.ts"
parse_profile "$TMP_DIR/repo/sama.profile.toml"
collect_input "$TMP_DIR/repo" "$TMP_DIR/repo/src"
run_check_modeled_boundary
assert_eq "0" "${#CHECK_VIOLATIONS[@]}" "Modeled (boundary): JSON.parse inside string literal ignored"

# — Check 5: Atomic (oversized file fails) -----------------------
setup_clean_repo
# Make a file with 701 newlines.
yes "x" 2>/dev/null | head -n 701 > "$TMP_DIR/repo/src/b_huge.ts"
parse_profile "$TMP_DIR/repo/sama.profile.toml"
collect_input "$TMP_DIR/repo" "$TMP_DIR/repo/src"
run_check_atomic
v_count="${#CHECK_VIOLATIONS[@]}"
assert_eq "1" "$v_count" "Atomic: 701-line file flagged"

# — Check 6: Law (§1.2) — upward import fails --------------------
setup_clean_repo
printf 'import { x } from "./d_entry.ts";\nexport const y = x;\n' > "$TMP_DIR/repo/src/b_bad.ts"
printf 'export const x = 1;\n' > "$TMP_DIR/repo/src/d_entry.ts"
parse_profile "$TMP_DIR/repo/sama.profile.toml"
collect_input "$TMP_DIR/repo" "$TMP_DIR/repo/src"
run_check_law
v_count="${#CHECK_VIOLATIONS[@]}"
# Should be ≥ 1: the upward edge from b_bad → d_entry.
if [ "$v_count" -ge 1 ]; then
  TESTS_RUN=$((TESTS_RUN + 1))
  printf "  ok  Law: upward import flagged (%d violation(s))\n" "$v_count"
else
  TESTS_RUN=$((TESTS_RUN + 1))
  TESTS_FAILED=$((TESTS_FAILED + 1))
  printf "  FAIL Law: upward import not flagged\n"
fi

# Downward import is OK.
setup_clean_repo
printf 'import { x } from "./a_pure.ts";\nexport const y = x;\n' > "$TMP_DIR/repo/src/b_good.ts"
printf 'export const x = 1;\n' > "$TMP_DIR/repo/src/a_pure.ts"
parse_profile "$TMP_DIR/repo/sama.profile.toml"
collect_input "$TMP_DIR/repo" "$TMP_DIR/repo/src"
run_check_law
assert_eq "0" "${#CHECK_VIOLATIONS[@]}" "Law: downward import passes"

# — Check 7: Consistency (declared layer < actual import ceiling) ----
setup_clean_repo
printf 'import { x } from "./d_entry.ts";\nexport const y = x;\n' > "$TMP_DIR/repo/src/b_lies.ts"
printf 'export const x = 1;\n' > "$TMP_DIR/repo/src/d_entry.ts"
parse_profile "$TMP_DIR/repo/sama.profile.toml"
collect_input "$TMP_DIR/repo" "$TMP_DIR/repo/src"
run_check_consistency
v_count="${#CHECK_VIOLATIONS[@]}"
if [ "$v_count" -ge 1 ]; then
  TESTS_RUN=$((TESTS_RUN + 1))
  printf "  ok  Consistency: layer-lie flagged\n"
else
  TESTS_RUN=$((TESTS_RUN + 1))
  TESTS_FAILED=$((TESTS_FAILED + 1))
  printf "  FAIL Consistency: layer-lie not flagged\n"
fi

# — Summary -------------------------------------------------------
echo
if [ "$TESTS_FAILED" -eq 0 ]; then
  printf "b32_checks.test.sh: %d/%d passed ✓\n" "$TESTS_RUN" "$TESTS_RUN"
  exit 0
else
  printf "b32_checks.test.sh: %d/%d passed, %d FAILED ✗\n" \
    "$((TESTS_RUN - TESTS_FAILED))" "$TESTS_RUN" "$TESTS_FAILED"
  exit 1
fi