#!/usr/bin/env bash set -euo pipefail # Master test/security runner. # - npm audit (high+) # - lint / type-check / format check / tests # - Trivy file-system scan (HIGH/CRITICAL) # - OWASP ZAP baseline (unauthenticated) # # Outputs are written under reports/runs// and summarized in summary.html. # # Env vars: # TARGET - URL to scan with ZAP (default: https://test.lomavuokraus.fi) # TRIVY_TARGET - Path or image to scan (default: current directory) # TRIVY_MODE - "fs" (default) or "image" # ZAP_IMAGE - Override ZAP image (default in zap-baseline.sh) # TIMEOUT_MINUTES - ZAP timeout minutes (default in zap-baseline.sh) RUN_TS=$(date +"%Y%m%d-%H%M%S") RUN_DIR="reports/runs/${RUN_TS}" mkdir -p "$RUN_DIR" SUMMARY_ROWS=() SUMMARY_TEXT_ROWS=() PASS_COUNT=0 FAIL_COUNT=0 SKIP_COUNT=0 log() { echo "[$(date +"%H:%M:%S")] $*" } record_result() { local name="$1"; shift local status="$1"; shift local detail="$1"; shift local detail_text="$1"; shift SUMMARY_ROWS+=("${name}${status}${detail}") SUMMARY_TEXT_ROWS+=("${name}: ${status}${detail_text:+ - ${detail_text}}") case "$status" in PASS) PASS_COUNT=$((PASS_COUNT + 1)) ;; FAIL) FAIL_COUNT=$((FAIL_COUNT + 1)) ;; SKIP) SKIP_COUNT=$((SKIP_COUNT + 1)) ;; esac } update_index() { local runs_dir="reports/runs" local index_file="reports/index.html" local items="" if [ -d "$runs_dir" ]; then while IFS= read -r run_dir; do local meta_file="${runs_dir}/${run_dir}/meta.txt" [ -f "$meta_file" ] || continue local run_ts target pass fail skip run_ts=$(grep -E '^RUN_TS=' "$meta_file" | head -n1 | cut -d= -f2-) target=$(grep -E '^TARGET=' "$meta_file" | head -n1 | cut -d= -f2-) pass=$(grep -E '^PASS_COUNT=' "$meta_file" | head -n1 | cut -d= -f2-) fail=$(grep -E '^FAIL_COUNT=' "$meta_file" | head -n1 | cut -d= -f2-) skip=$(grep -E '^SKIP_COUNT=' "$meta_file" | head -n1 | cut -d= -f2-) run_ts="${run_ts:-$run_dir}" target="${target:-unknown}" pass="${pass:-0}" fail="${fail:-0}" skip="${skip:-0}" items+=$'\n'"
  • Target: ${target} | Pass: ${pass} | Fail: ${fail} | Skip: ${skip}
  • " done < <(ls "$runs_dir" 2>/dev/null | sort -r) fi cat >"$index_file" < Lomavuokraus Test Suite Runs

    Test Suite Runs

    EOF } notify_redmine() { local fail_lines=() local failures_file="$RUN_DIR/failures.txt" for row in "${SUMMARY_TEXT_ROWS[@]}"; do if [[ "$row" == *"FAIL"* ]]; then fail_lines+=("$row") fi done [ "${#fail_lines[@]}" -gt 0 ] || return printf "%s\n" "${fail_lines[@]}" >"$failures_file" local tracker="bug" for row in "${fail_lines[@]}"; do case "$row" in npm\ audit:*) tracker="security" ;; Trivy*) tracker="security" ;; OWASP\ ZAP\ baseline*) tracker="security" ;; esac done if command -v node >/dev/null 2>&1 && [ -x "${BASH_SOURCE%/*}/redmine-report.js" ]; then log "Reporting failures to Redmine (${tracker})..." if node "${BASH_SOURCE%/*}/redmine-report.js" create-test-issue \ --suite "run-test-suite" \ --run "$RUN_TS" \ --fail-count "$FAIL_COUNT" \ --failures-file "$failures_file" \ --summary-file "$SUMMARY_FILE" \ --target "$TARGET" \ --tracker "$tracker" \ --fingerprint-seed "${TARGET}|${fail_lines[*]}"; then log "Redmine notification complete." else log "Redmine notification failed or skipped (see output above)." fi else log "Redmine reporter not available; skipping Redmine notification." fi } # 1) npm audit if command -v npm >/dev/null 2>&1; then log "Running npm audit (high)..." AUDIT_JSON="$RUN_DIR/npm-audit.json" AUDIT_TXT="$RUN_DIR/npm-audit.txt" if npm audit --audit-level=high --json >"$AUDIT_JSON" 2>"$AUDIT_TXT"; then record_result "npm audit" "PASS" "text | json" "reports: ${AUDIT_TXT}, ${AUDIT_JSON}" else record_result "npm audit" "FAIL" "text | json" "reports: ${AUDIT_TXT}, ${AUDIT_JSON}" fi else log "npm not found; skipping npm audit" record_result "npm audit" "SKIP" "npm not available" "npm not available" fi # 2) Lint / type-check / format / tests run_npm_check() { local name="$1"; shift local outfile="$RUN_DIR/${name}.txt" if ! command -v npm >/dev/null 2>&1; then log "npm not found; skipping ${name}" record_result "${name}" "SKIP" "npm not available" "npm not available" return fi if npm run 2>/dev/null | grep -qE "^ ${name}$"; then log "Running ${name}..." if npm run "${name}" >"$outfile" 2>&1; then record_result "${name}" "PASS" "log" "log: ${outfile}" else record_result "${name}" "FAIL" "log" "log: ${outfile}" fi else log "npm script '${name}' not defined; skipping" record_result "${name}" "SKIP" "script not defined" "script not defined" fi } run_npm_check "lint" run_npm_check "type-check" run_npm_check "format:check" run_npm_check "test" # 3) Trivy (fs by default) TRIVY_TARGET="${TRIVY_TARGET:-.}" TRIVY_MODE="${TRIVY_MODE:-fs}" if command -v trivy >/dev/null 2>&1; then log "Running Trivy (${TRIVY_MODE}) on ${TRIVY_TARGET}..." TRIVY_TXT="$RUN_DIR/trivy.txt" TRIVY_IGNORE_ARGS=() if [ -f ".trivyignore" ]; then TRIVY_IGNORE_ARGS+=(--ignorefile .trivyignore) fi if trivy "${TRIVY_MODE}" --severity HIGH,CRITICAL --timeout 5m "${TRIVY_IGNORE_ARGS[@]}" "$TRIVY_TARGET" >"$TRIVY_TXT"; then record_result "Trivy (${TRIVY_MODE})" "PASS" "report" "report: ${TRIVY_TXT}" else record_result "Trivy (${TRIVY_MODE})" "FAIL" "report" "report: ${TRIVY_TXT}" fi else log "Trivy not found; skipping" record_result "Trivy" "SKIP" "trivy not available" "trivy not available" fi # 4) OWASP ZAP baseline TARGET="${TARGET:-https://test.lomavuokraus.fi}" ZAP_DIR="$RUN_DIR/zap" mkdir -p "$ZAP_DIR" log "Running ZAP baseline against ${TARGET}..." if TARGET="$TARGET" REPORT_DIR="$ZAP_DIR" "${BASH_SOURCE%/*}/zap-baseline.sh"; then record_result "OWASP ZAP baseline" "PASS" "HTML | JSON" "reports: ${ZAP_DIR}/zap-report.html, ${ZAP_DIR}/zap-report.json" else record_result "OWASP ZAP baseline" "FAIL" "HTML | JSON" "reports: ${ZAP_DIR}/zap-report.html, ${ZAP_DIR}/zap-report.json" fi # Summary HTML SUMMARY_FILE="$RUN_DIR/summary.html" cat >"$SUMMARY_FILE" < Test Suite Summary - ${RUN_TS}

    Test Suite Summary

    Run: ${RUN_TS}
    Target: ${TARGET}
    ${SUMMARY_ROWS[*]}
    CheckStatusDetails
    EOF META_FILE="$RUN_DIR/meta.txt" cat >"$META_FILE" <