Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 3 additions & 33 deletions test/smoke-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -166,39 +166,9 @@ done
# Check preamble uses CLAUDE_PLUGIN_ROOT
check "preamble supports CLAUDE_PLUGIN_ROOT" "grep -q 'CLAUDE_PLUGIN_ROOT' '$IDSTACK_DIR/templates/preamble.md'"

# Migration tests
FIXTURE_DIR="$IDSTACK_DIR/test/fixtures"
if [ -d "$FIXTURE_DIR" ] && command -v python3 &>/dev/null; then
# Test v1.0 → v1.4 chained migration
TMPDIR_MIG=$(mktemp -d)
cp "$FIXTURE_DIR/manifest-v1.0.json" "$TMPDIR_MIG/project.json"
"$IDSTACK_DIR/bin/idstack-migrate" "$TMPDIR_MIG/project.json" >/dev/null 2>&1
check "v1.0→v1.4: version bumped" "python3 -c \"import json; d=json.load(open('$TMPDIR_MIG/project.json')); assert d['version']=='1.4'\""
check "v1.0→v1.4: has preferences" "python3 -c \"import json; d=json.load(open('$TMPDIR_MIG/project.json')); assert 'preferences' in d\""
check "v1.0→v1.4: preserves project_name" "python3 -c \"import json; d=json.load(open('$TMPDIR_MIG/project.json')); assert d['project_name']=='Test Course v1.0'\""
rm -rf "$TMPDIR_MIG"

# Test v1.2 → v1.4 migration
TMPDIR_MIG=$(mktemp -d)
cp "$FIXTURE_DIR/manifest-v1.2.json" "$TMPDIR_MIG/project.json"
"$IDSTACK_DIR/bin/idstack-migrate" "$TMPDIR_MIG/project.json" >/dev/null 2>&1
check "v1.2→v1.4: version bumped" "python3 -c \"import json; d=json.load(open('$TMPDIR_MIG/project.json')); assert d['version']=='1.4'\""
check "v1.2→v1.4: has preferences" "python3 -c \"import json; d=json.load(open('$TMPDIR_MIG/project.json')); assert d['preferences']['verbosity']=='normal'\""
check "v1.2→v1.4: idempotent" "python3 -c \"import json; d=json.load(open('$TMPDIR_MIG/project.json')); assert d['version']=='1.4'\" && '$IDSTACK_DIR/bin/idstack-migrate' '$TMPDIR_MIG/project.json' >/dev/null 2>&1 && python3 -c \"import json; d=json.load(open('$TMPDIR_MIG/project.json')); assert d['version']=='1.4'\""
rm -rf "$TMPDIR_MIG"

# Test v1.3-drifted → v1.4 cleanup migration (renames red_team_audit.summary.*_count
# to red_team_audit.findings_summary.*, moves _import_quality_flags into
# import_metadata.quality_flag_details).
if [ -f "$FIXTURE_DIR/manifest-v1.3-drifted.json" ]; then
TMPDIR_MIG=$(mktemp -d)
cp "$FIXTURE_DIR/manifest-v1.3-drifted.json" "$TMPDIR_MIG/project.json"
"$IDSTACK_DIR/bin/idstack-migrate" "$TMPDIR_MIG/project.json" >/dev/null 2>&1
check "v1.3-drifted→v1.4: version bumped" "python3 -c \"import json; d=json.load(open('$TMPDIR_MIG/project.json')); assert d['version']=='1.4'\""
check "v1.3-drifted→v1.4: red_team summary renamed to findings_summary" "python3 -c \"import json; d=json.load(open('$TMPDIR_MIG/project.json')); rt=d['red_team_audit']; assert 'summary' not in rt; assert rt['findings_summary']=={'critical': 3, 'warning': 5, 'info': 2}\""
check "v1.3-drifted→v1.4: _import_quality_flags moved into import_metadata" "python3 -c \"import json; d=json.load(open('$TMPDIR_MIG/project.json')); assert '_import_quality_flags' not in d; details=d['import_metadata']['quality_flag_details']; assert len(details)==2 and details[0]['key']=='orphan_module_8'\""
rm -rf "$TMPDIR_MIG"
fi
# Migration tests (delegated to unit test suite)
if [ -x "$IDSTACK_DIR/test/test-migrate.sh" ]; then
check "idstack-migrate unit tests pass" "'$IDSTACK_DIR/test/test-migrate.sh'"
fi
Comment on lines +170 to 172

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If test-migrate.sh is missing or not executable, the smoke test will silently skip these unit tests instead of failing. It is safer to run the test script directly so that any execution failure or missing file is caught.

Suggested change
if [ -x "$IDSTACK_DIR/test/test-migrate.sh" ]; then
check "idstack-migrate unit tests pass" "'$IDSTACK_DIR/test/test-migrate.sh'"
fi
check "idstack-migrate unit tests pass" "'$IDSTACK_DIR/test/test-migrate.sh'"


# Template freshness check (covers all targets: claude + codex)
Expand Down
84 changes: 84 additions & 0 deletions test/test-migrate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# Unit tests for bin/idstack-migrate.
# Run from the repo root (or sourced by smoke-test.sh).

set -e

PASS=0
FAIL=0
TOTAL=0

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
MIGRATE="$REPO_ROOT/bin/idstack-migrate"
FIXTURE_DIR="$REPO_ROOT/test/fixtures"

assert() {
TOTAL=$((TOTAL + 1))
if eval "$2" >/dev/null 2>&1; then
PASS=$((PASS + 1))
echo " PASS: $1"
else
FAIL=$((FAIL + 1))
echo " FAIL: $1"
fi
}

# Skip the suite if python3 is missing (the tool requires python3).
if ! command -v python3 >/dev/null 2>&1; then
echo "test-migrate: python3 not available, skipping"
exit 0
fi

if [ ! -x "$MIGRATE" ]; then
echo "test-migrate: $MIGRATE missing or not executable"
exit 1
fi

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

echo "test-migrate"
echo ""

# --- Test: v1.0 → v1.4 chained migration ---
cp "$FIXTURE_DIR/manifest-v1.0.json" "$WORK/project.json"
"$MIGRATE" "$WORK/project.json" >/dev/null 2>&1
assert "v1.0→v1.4: version bumped" "python3 -c \"import json; d=json.load(open('$WORK/project.json')); assert d['version']=='1.4'\""
assert "v1.0→v1.4: has preferences" "python3 -c \"import json; d=json.load(open('$WORK/project.json')); assert 'preferences' in d\""
assert "v1.0→v1.4: preserves project_name" "python3 -c \"import json; d=json.load(open('$WORK/project.json')); assert d['project_name']=='Test Course v1.0'\""

# --- Test: v1.2 → v1.4 migration ---
cp "$FIXTURE_DIR/manifest-v1.2.json" "$WORK/project.json"
"$MIGRATE" "$WORK/project.json" >/dev/null 2>&1
assert "v1.2→v1.4: version bumped" "python3 -c \"import json; d=json.load(open('$WORK/project.json')); assert d['version']=='1.4'\""
assert "v1.2→v1.4: has preferences" "python3 -c \"import json; d=json.load(open('$WORK/project.json')); assert d['preferences']['verbosity']=='normal'\""

# --- Test: idempotent ---
assert "v1.2→v1.4: idempotent" "python3 -c \"import json; d=json.load(open('$WORK/project.json')); assert d['version']=='1.4'\" && '$MIGRATE' '$WORK/project.json' >/dev/null 2>&1 && python3 -c \"import json; d=json.load(open('$WORK/project.json')); assert d['version']=='1.4'\""

# --- Test: v1.3-drifted → v1.4 cleanup migration ---
if [ -f "$FIXTURE_DIR/manifest-v1.3-drifted.json" ]; then
cp "$FIXTURE_DIR/manifest-v1.3-drifted.json" "$WORK/project.json"
"$MIGRATE" "$WORK/project.json" >/dev/null 2>&1
assert "v1.3-drifted→v1.4: version bumped" "python3 -c \"import json; d=json.load(open('$WORK/project.json')); assert d['version']=='1.4'\""
assert "v1.3-drifted→v1.4: red_team summary renamed to findings_summary" "python3 -c \"import json; d=json.load(open('$WORK/project.json')); rt=d['red_team_audit']; assert 'summary' not in rt; assert rt['findings_summary']=={'critical': 3, 'warning': 5, 'info': 2}\""
assert "v1.3-drifted→v1.4: _import_quality_flags moved into import_metadata" "python3 -c \"import json; d=json.load(open('$WORK/project.json')); assert '_import_quality_flags' not in d; details=d['import_metadata']['quality_flag_details']; assert len(details)==2 and details[0]['key']=='orphan_module_8'\""
fi

# --- Test: file does not exist ---
rm -f "$WORK/project.json"
"$MIGRATE" "$WORK/project.json" >/dev/null 2>&1
EC=$?
assert "missing manifest exits 0" "[ $EC -eq 0 ]"
Comment on lines +70 to +72

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Under set -e, if the $MIGRATE command fails, the script will immediately terminate at line 70 instead of continuing to the assertion. To safely capture the exit code without triggering set -e on failure, initialize EC and use the || operator.

Suggested change
"$MIGRATE" "$WORK/project.json" >/dev/null 2>&1
EC=$?
assert "missing manifest exits 0" "[ $EC -eq 0 ]"
EC=0
"$MIGRATE" "$WORK/project.json" >/dev/null 2>&1 || EC=$?
assert "missing manifest exits 0" "[ $EC -eq 0 ]"


# --- Test: malformed json gracefully degraded (fallback to cat, exits 0) ---
echo "invalid json" > "$WORK/project.json"
# capture output to check fallback mechanism
OUT=$("$MIGRATE" "$WORK/project.json" 2>/dev/null)
EC=$?
assert "malformed manifest exits 0" "[ $EC -eq 0 ]"
Comment on lines +77 to +79

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If $MIGRATE fails here, the command substitution inside the variable assignment will trigger set -e and terminate the script immediately. Initialize EC and use || to safely capture the exit code.

Suggested change
OUT=$("$MIGRATE" "$WORK/project.json" 2>/dev/null)
EC=$?
assert "malformed manifest exits 0" "[ $EC -eq 0 ]"
EC=0
OUT=$("$MIGRATE" "$WORK/project.json" 2>/dev/null) || EC=$?
assert "malformed manifest exits 0" "[ $EC -eq 0 ]"

assert "malformed manifest outputs original" "[ \"\$OUT\" = 'invalid json' ]"

echo ""
echo "migrate: $PASS/$TOTAL passed, $FAIL failed"
[ "$FAIL" -eq 0 ] && exit 0 || exit 1