run.sh 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. #!/usr/bin/env bash
  2. # net-ops :: tests/run.sh
  3. # Lightweight self-tests. Run from the repo root:
  4. # bash skills/net-ops/tests/run.sh
  5. #
  6. # These verify structural and output invariants of the probe scripts WITHOUT
  7. # trying to simulate broken network state. They catch regressions in:
  8. # - bash syntax / unbound vars / set -u trips
  9. # - section labels and ordering
  10. # - --redact actually masking private addrs / tailnet names
  11. # - --json producing parseable NDJSON
  12. # - summary block format
  13. # - dispatcher routing to the right per-OS script
  14. set -u
  15. PASS=0
  16. FAIL=0
  17. FAILED_TESTS=()
  18. assert() {
  19. local name="$1"; shift
  20. if "$@"; then
  21. PASS=$((PASS+1))
  22. printf " [PASS] %s\n" "$name"
  23. else
  24. FAIL=$((FAIL+1))
  25. FAILED_TESTS+=("$name")
  26. printf " [FAIL] %s\n" "$name"
  27. fi
  28. }
  29. contains() { local hay="$1" needle="$2"; [[ "$hay" == *"$needle"* ]]; }
  30. not_contains() { local hay="$1" needle="$2"; [[ "$hay" != *"$needle"* ]]; }
  31. # Locate skill root regardless of invocation dir
  32. here="$(cd "$(dirname "$0")" && pwd)"
  33. root="$(cd "$here/.." && pwd)"
  34. echo "=== net-ops self-tests ==="
  35. echo "Root: $root"
  36. # Determine the local OS probe for testing
  37. case "$(uname -s)" in
  38. Darwin) probe="$root/scripts/macos/probe.sh"; audit="$root/scripts/macos/dns-audit.sh" ;;
  39. Linux) probe="$root/scripts/linux/probe.sh"; audit="$root/scripts/linux/dns-audit.sh" ;;
  40. *) echo "Skipping: unsupported OS for local probe tests." ; exit 0 ;;
  41. esac
  42. # ---------------------------------------------------------------------------
  43. echo
  44. echo "--- Probe structural tests ---"
  45. # ---------------------------------------------------------------------------
  46. out=$(bash "$probe" 2>&1)
  47. assert "probe runs without bash error" \
  48. not_contains "$out" "syntax error"
  49. assert "probe runs without unbound variable error" \
  50. not_contains "$out" "unbound variable"
  51. assert "probe emits summary block" \
  52. contains "$out" "=== SUMMARY ==="
  53. assert "probe emits PASS/FAIL counts" \
  54. contains "$out" "PASS:"
  55. check_all_sections() {
  56. local out="$1"
  57. for s in "1. LINK LAYER" "2. IP / ICMP" "3. TCP/UDP SOCKET" "4. DNS INFRASTRUCTURE" "6. APPLICATION" "7. KNOWN VPN"; do
  58. contains "$out" "=== $s" || return 1
  59. done
  60. # Section 5 has OS-specific naming; match on the common anchor.
  61. contains "$out" "(the hook layer)" || return 1
  62. return 0
  63. }
  64. assert "probe contains all 7 sections" check_all_sections "$out"
  65. # ---------------------------------------------------------------------------
  66. echo
  67. echo "--- --redact tests ---"
  68. # ---------------------------------------------------------------------------
  69. redacted=$(bash "$probe" --redact 2>&1)
  70. # Common private patterns that should NEVER appear in redacted output.
  71. # (We use specific octets that are unlikely to appear in unrelated contexts.)
  72. assert "--redact masks 192.168.x.x" \
  73. bash -c '! grep -E "\b192\.168\.[0-9]+\.[0-9]+\b" <<< "$0" | grep -v "192.168.X.X" >/dev/null' "$redacted"
  74. assert "--redact masks .ts.net tailnet names" \
  75. bash -c '! grep -E "\b[a-z0-9-]+\.ts\.net\b" <<< "$0" | grep -v "REDACTED.ts.net" >/dev/null' "$redacted"
  76. assert "--redact preserves 100.100.100.100 anchor" \
  77. bash -c '[[ "$0" != *"100.X.X.X"* ]] || grep -q "100.100.100.100" <<< "$0"' "$redacted"
  78. assert "--redact preserves 1.1.1.1 public anchor" \
  79. contains "$redacted" "1.1.1.1"
  80. # ---------------------------------------------------------------------------
  81. echo
  82. echo "--- --json tests ---"
  83. # ---------------------------------------------------------------------------
  84. json_out=$(bash "$probe" --json 2>&1)
  85. assert "--json emits at least one section record" \
  86. contains "$json_out" '"type":"section"'
  87. assert "--json emits at least one check record" \
  88. contains "$json_out" '"type":"check"'
  89. assert "--json emits a summary record" \
  90. contains "$json_out" '"type":"summary"'
  91. assert "--json summary contains pass count" \
  92. bash -c 'grep -q "\"type\":\"summary\".*\"pass\":[0-9]" <<< "$0"' "$json_out"
  93. # ---------------------------------------------------------------------------
  94. echo
  95. echo "--- Dispatcher test ---"
  96. # ---------------------------------------------------------------------------
  97. disp_out=$("$root/scripts/probe" 2>&1 | tail -5)
  98. assert "dispatcher routes to per-OS probe (summary present)" \
  99. contains "$disp_out" "PASS:"
  100. # ---------------------------------------------------------------------------
  101. echo
  102. echo "--- dns-audit smoke test ---"
  103. # ---------------------------------------------------------------------------
  104. audit_out=$(bash "$audit" 2>&1)
  105. assert "dns-audit runs without error" \
  106. not_contains "$audit_out" "syntax error"
  107. assert "dns-audit emits attribution hints section" \
  108. contains "$audit_out" "ATTRIBUTION HINTS"
  109. # ---------------------------------------------------------------------------
  110. echo
  111. echo "--- Edge cases ---"
  112. # ---------------------------------------------------------------------------
  113. # --json should emit ONLY JSON (no chatter leaking through)
  114. json_pure=$(bash "$probe" --json 2>&1)
  115. non_json=$(echo "$json_pure" | grep -vc '^{')
  116. assert "--json produces pure NDJSON (no non-JSON chatter)" \
  117. bash -c '[[ "$0" -eq 0 ]]' "$non_json"
  118. # --json + --redact: redacted private addrs AND only JSON
  119. combo=$(bash "$probe" --json --redact 2>&1)
  120. combo_non_json=$(echo "$combo" | grep -vc '^{')
  121. combo_leaks=$(echo "$combo" | grep -E "\b192\.168\.[0-9]+\.[0-9]+\b" | grep -v "192.168.X.X")
  122. assert "--json + --redact produces pure NDJSON" \
  123. bash -c '[[ "$0" -eq 0 ]]' "$combo_non_json"
  124. assert "--json + --redact has no private-IP leaks" \
  125. bash -c '[[ -z "$0" ]]' "$combo_leaks"
  126. # Unknown flag should not crash
  127. assert "unknown --frobnicate flag does not crash" \
  128. bash -c 'bash "$0" --frobnicate 2>&1 | grep -q "PASS\\|FAIL"' "$probe"
  129. # Help flag prints usage and exits cleanly
  130. help_out=$(bash "$probe" --help 2>&1)
  131. assert "--help mentions --redact" \
  132. contains "$help_out" "--redact"
  133. assert "--help mentions --json" \
  134. contains "$help_out" "--json"
  135. assert "--help mentions --quick" \
  136. contains "$help_out" "--quick"
  137. # Dispatcher works from a different cwd
  138. disp_remote=$(cd /tmp && "$root/scripts/probe" 2>&1 | tail -5)
  139. assert "dispatcher works from /tmp (cwd-independent)" \
  140. contains "$disp_remote" "PASS:"
  141. # ---------------------------------------------------------------------------
  142. echo
  143. echo "=== TOTAL: $PASS pass, $FAIL fail ==="
  144. if [[ "$FAIL" -gt 0 ]]; then
  145. echo "Failed tests:"
  146. for t in "${FAILED_TESTS[@]}"; do echo " - $t"; done
  147. exit 1
  148. fi
  149. exit 0