Skip to content

Commit 09ee72c

Browse files
kulvirgitclaude
andcommitted
feat: add local E2E sanity test harness
Docker-based "new user simulator" that tests the shipped npm artifact: - Phase 1: verify-install (9 checks: binary, skills, napi, dbt, git) - Phase 2: smoke-tests (10 E2E tests via altimate run, parallelized) - Phase 3: resilience (8 tests: SQLite, WAL, sessions, compaction, config) - PR-aware test generation (git diff → targeted tests) - Local CI pipeline (bun run ci → typecheck + tests + markers) - Machine-aware parallelism (2-6 concurrent based on cores/RAM) 27 tests, all passing in ~2:48 on 20-core machine. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7afa3d6 commit 09ee72c

22 files changed

+1196
-1
lines changed

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
"prepare": "husky",
1515
"random": "echo 'Random script'",
1616
"hello": "echo 'Hello World!'",
17-
"test": "echo 'do not run tests from root' && exit 1"
17+
"test": "echo 'do not run tests from root' && exit 1",
18+
"ci": "test/sanity/ci-local.sh",
19+
"ci:full": "test/sanity/ci-local.sh full",
20+
"ci:pr": "test/sanity/ci-local.sh pr",
21+
"sanity": "docker compose -f test/sanity/docker-compose.yml up --build --abort-on-container-exit --exit-code-from sanity",
22+
"sanity:upgrade": "docker compose -f test/sanity/docker-compose.yml -f test/sanity/docker-compose.upgrade.yml up --build --abort-on-container-exit --exit-code-from sanity"
1823
},
1924
"workspaces": {
2025
"packages": [

test/sanity/.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Required for smoke tests (LLM-dependent)
2+
ANTHROPIC_API_KEY=sk-ant-...
3+
4+
# Optional: Snowflake credentials for cloud warehouse testing
5+
# ALTIMATE_CODE_CONN_SNOWFLAKE_TEST='{"account":"...","user":"...","password":"...","warehouse":"...","database":"..."}'

test/sanity/Dockerfile

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
FROM oven/bun:1.3.10-debian
2+
3+
# System deps (what a real user would have)
4+
RUN apt-get update && apt-get install -y \
5+
git python3 python3-pip python3-venv curl sqlite3 \
6+
&& rm -rf /var/lib/apt/lists/*
7+
8+
# dbt in venv (matches real user setup)
9+
RUN python3 -m venv /opt/dbt && \
10+
/opt/dbt/bin/pip install --quiet dbt-core dbt-duckdb dbt-postgres
11+
ENV PATH="/opt/dbt/bin:$PATH"
12+
13+
# Fresh user, clean HOME — simulates new user
14+
ENV HOME=/home/testuser
15+
RUN useradd -m testuser
16+
USER testuser
17+
WORKDIR /home/testuser
18+
19+
# Copy the built binary directly (simulates what postinstall does)
20+
# The real npm publish pipeline rewrites workspace:* deps — we can't use npm pack directly.
21+
# Instead, copy the built binary + run postinstall manually.
22+
COPY --chown=testuser packages/opencode/dist/@altimateai/ /home/testuser/.altimate-install/dist/@altimateai/
23+
COPY --chown=testuser packages/opencode/script/postinstall.mjs /home/testuser/.altimate-install/postinstall.mjs
24+
COPY --chown=testuser packages/opencode/package.json /home/testuser/.altimate-install/package.json
25+
COPY --chown=testuser .opencode/skills/ /home/testuser/.altimate-install/skills/
26+
27+
# Install altimate-core native binding (required at runtime)
28+
RUN cd /home/testuser/.altimate-install && \
29+
echo '{"dependencies":{"@altimateai/altimate-core":"latest"}}' > package.json && \
30+
bun install 2>&1 | tail -5
31+
32+
# Link binary to PATH and copy skills to ~/.altimate/builtin/
33+
RUN mkdir -p /home/testuser/.local/bin && \
34+
cp /home/testuser/.altimate-install/dist/@altimateai/altimate-code-linux-arm64/bin/altimate /home/testuser/.local/bin/altimate && \
35+
chmod +x /home/testuser/.local/bin/altimate && \
36+
mkdir -p /home/testuser/.altimate/builtin && \
37+
cp -r /home/testuser/.altimate-install/skills/* /home/testuser/.altimate/builtin/
38+
ENV PATH="/home/testuser/.local/bin:$PATH"
39+
ENV NODE_PATH="/home/testuser/.altimate-install/node_modules"
40+
41+
# Copy test scripts
42+
COPY --chown=testuser test/sanity/ /home/testuser/sanity/
43+
RUN chmod +x /home/testuser/sanity/run.sh \
44+
/home/testuser/sanity/phases/*.sh \
45+
/home/testuser/sanity/pr-tests/*.sh \
46+
/home/testuser/sanity/lib/*.sh
47+
48+
ENTRYPOINT ["/home/testuser/sanity/run.sh"]

test/sanity/Dockerfile.upgrade

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
FROM oven/bun:1.3.10-debian
2+
3+
RUN apt-get update && apt-get install -y \
4+
git python3 python3-pip python3-venv curl sqlite3 \
5+
&& rm -rf /var/lib/apt/lists/*
6+
7+
RUN python3 -m venv /opt/dbt && \
8+
/opt/dbt/bin/pip install --quiet dbt-core dbt-duckdb dbt-postgres
9+
ENV PATH="/opt/dbt/bin:$PATH"
10+
11+
ENV HOME=/home/testuser
12+
RUN useradd -m testuser
13+
USER testuser
14+
WORKDIR /home/testuser
15+
16+
# Install previous version first
17+
ARG PRIOR_VERSION=latest
18+
RUN npm install -g @altimateai/altimate-code@${PRIOR_VERSION}
19+
20+
# Record old version for comparison
21+
RUN altimate --version > /tmp/old-version.txt 2>/dev/null || echo "unknown" > /tmp/old-version.txt
22+
23+
# Run once to seed DB, config, session data
24+
RUN git init /tmp/seed-project && \
25+
cd /tmp/seed-project && \
26+
git config user.name "seed" && \
27+
git config user.email "seed@test.local" && \
28+
echo '{}' > package.json && \
29+
git add -A && git commit -q -m "seed"
30+
31+
# Upgrade to new version
32+
COPY --chown=testuser dist/package.tgz /tmp/altimate-code.tgz
33+
RUN npm install -g /tmp/altimate-code.tgz
34+
35+
# Copy test scripts
36+
COPY --chown=testuser test/sanity/ /home/testuser/sanity/
37+
RUN chmod +x /home/testuser/sanity/run.sh \
38+
/home/testuser/sanity/phases/*.sh \
39+
/home/testuser/sanity/pr-tests/*.sh \
40+
/home/testuser/sanity/lib/*.sh
41+
42+
ENV OLD_VERSION_FILE=/tmp/old-version.txt
43+
44+
ENTRYPOINT ["/home/testuser/sanity/run.sh", "--upgrade"]

test/sanity/ci-local.sh

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/bin/bash
2+
# Local CI pipeline — ports .github/workflows/ci.yml to run locally
3+
set -euo pipefail
4+
5+
MODE="${1:-fast}"
6+
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
7+
EXIT_CODE=0
8+
9+
echo "========================================"
10+
echo " Local CI Pipeline — mode: $MODE"
11+
echo " Time: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
12+
echo "========================================"
13+
14+
run_step() {
15+
local name="$1"; shift
16+
echo ""
17+
echo "--- $name ---"
18+
if "$@"; then
19+
echo " >>> $name: PASSED"
20+
else
21+
echo " >>> $name: FAILED"
22+
EXIT_CODE=1
23+
fi
24+
}
25+
26+
# ── Fast mode (default — what pre-push hook runs) ──────────────
27+
28+
echo ""
29+
echo "=== Fast CI ==="
30+
31+
run_step "Typecheck" bun turbo typecheck
32+
33+
run_step "Unit Tests (opencode)" bash -c "cd $REPO_ROOT/packages/opencode && bun test --timeout 30000"
34+
35+
run_step "Unit Tests (dbt-tools)" bash -c "cd $REPO_ROOT/packages/dbt-tools && bun run test"
36+
37+
# Marker guard (needs upstream remote)
38+
if git remote | grep -q upstream; then
39+
run_step "Marker Guard" bun run "$REPO_ROOT/script/upstream/analyze.ts" --markers --base origin/main --strict
40+
else
41+
echo ""
42+
echo "--- Marker Guard ---"
43+
echo " SKIP: upstream remote not configured"
44+
fi
45+
46+
# ── Full mode ──────────────────────────────────────────────────
47+
48+
if [ "$MODE" = "--full" ] || [ "$MODE" = "full" ]; then
49+
echo ""
50+
echo "=== Full CI (Docker) ==="
51+
52+
# Driver E2E with Docker containers
53+
run_step "Docker Services Up" docker compose -f "$REPO_ROOT/test/sanity/docker-compose.yml" up -d postgres mysql mssql redshift
54+
55+
echo " Waiting for services to be healthy..."
56+
sleep 10
57+
58+
run_step "Driver E2E (local)" bash -c "cd $REPO_ROOT/packages/opencode && \
59+
TEST_PG_HOST=127.0.0.1 TEST_PG_PORT=15432 TEST_PG_PASSWORD=testpass123 \
60+
bun test test/altimate/drivers-e2e.test.ts --timeout 30000"
61+
62+
run_step "Driver E2E (docker)" bash -c "cd $REPO_ROOT/packages/opencode && \
63+
TEST_MYSQL_HOST=127.0.0.1 TEST_MYSQL_PORT=13306 TEST_MYSQL_PASSWORD=testpass123 \
64+
TEST_MSSQL_HOST=127.0.0.1 TEST_MSSQL_PORT=11433 TEST_MSSQL_PASSWORD='TestPass123!' \
65+
TEST_REDSHIFT_HOST=127.0.0.1 TEST_REDSHIFT_PORT=15439 TEST_REDSHIFT_PASSWORD=testpass123 \
66+
bun test test/altimate/drivers-docker-e2e.test.ts --timeout 30000"
67+
68+
# Full sanity suite in Docker
69+
run_step "Sanity Suite (Docker)" docker compose -f "$REPO_ROOT/test/sanity/docker-compose.yml" \
70+
up --build --abort-on-container-exit --exit-code-from sanity
71+
72+
docker compose -f "$REPO_ROOT/test/sanity/docker-compose.yml" down --volumes --remove-orphans 2>/dev/null
73+
fi
74+
75+
# ── PR mode ────────────────────────────────────────────────────
76+
77+
if [ "$MODE" = "--pr" ] || [ "$MODE" = "pr" ]; then
78+
echo ""
79+
echo "=== PR-Aware Tests ==="
80+
81+
run_step "Generate PR tests" bash "$REPO_ROOT/test/sanity/pr-tests/generate.sh" origin/main
82+
run_step "Run PR tests" bash "$REPO_ROOT/test/sanity/pr-tests/run-pr-tests.sh"
83+
fi
84+
85+
# ── Summary ────────────────────────────────────────────────────
86+
87+
echo ""
88+
echo "========================================"
89+
if [ $EXIT_CODE -eq 0 ]; then
90+
echo " LOCAL CI: ALL PASSED"
91+
else
92+
echo " LOCAL CI: SOME STEPS FAILED"
93+
fi
94+
echo "========================================"
95+
96+
exit $EXIT_CODE

test/sanity/docker-compose.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
services:
2+
sanity:
3+
build:
4+
context: ../..
5+
dockerfile: test/sanity/Dockerfile
6+
environment:
7+
- ANTHROPIC_API_KEY
8+
- ALTIMATE_CODE_CONN_SNOWFLAKE_TEST
9+
- TEST_PG_HOST=postgres
10+
- TEST_PG_PORT=5432
11+
- TEST_PG_PASSWORD=testpass123
12+
- TEST_MYSQL_HOST=mysql
13+
- TEST_MYSQL_PORT=3306
14+
- TEST_MYSQL_PASSWORD=testpass123
15+
- TEST_MSSQL_HOST=mssql
16+
- TEST_MSSQL_PORT=1433
17+
- TEST_MSSQL_PASSWORD=TestPass123!
18+
- TEST_REDSHIFT_HOST=redshift
19+
- TEST_REDSHIFT_PORT=5432
20+
- TEST_REDSHIFT_PASSWORD=testpass123
21+
depends_on:
22+
postgres:
23+
condition: service_healthy
24+
mysql:
25+
condition: service_healthy
26+
mssql:
27+
condition: service_healthy
28+
redshift:
29+
condition: service_healthy
30+
31+
postgres:
32+
image: postgres:16-alpine
33+
environment:
34+
POSTGRES_PASSWORD: testpass123
35+
ports:
36+
- "15432:5432"
37+
healthcheck:
38+
test: pg_isready
39+
interval: 5s
40+
retries: 10
41+
42+
mysql:
43+
image: mysql:8.0
44+
environment:
45+
MYSQL_ROOT_PASSWORD: testpass123
46+
MYSQL_DATABASE: testdb
47+
ports:
48+
- "13306:3306"
49+
healthcheck:
50+
test: mysqladmin ping -h 127.0.0.1
51+
interval: 5s
52+
retries: 20
53+
54+
mssql:
55+
image: mcr.microsoft.com/azure-sql-edge:latest
56+
environment:
57+
ACCEPT_EULA: "Y"
58+
MSSQL_SA_PASSWORD: "TestPass123!"
59+
ports:
60+
- "11433:1433"
61+
healthcheck:
62+
test: /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'TestPass123!' -Q 'SELECT 1' || exit 1
63+
interval: 10s
64+
retries: 20
65+
66+
redshift:
67+
image: postgres:16-alpine
68+
environment:
69+
POSTGRES_PASSWORD: testpass123
70+
POSTGRES_DB: dev
71+
ports:
72+
- "15439:5432"
73+
healthcheck:
74+
test: pg_isready
75+
interval: 5s
76+
retries: 10
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"provider": {
3+
"anthropic": {
4+
"api_key": 12345
5+
}
6+
},
7+
"experimental": {
8+
"not_a_real_field": "should be ignored",
9+
"auto_mcp_discovery": "not-a-boolean"
10+
},
11+
"unknown_top_level": true
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Seed a large session to test compaction circuit breaker
2+
-- This creates a session with enough message data to trigger isOverflow()
3+
-- Note: This is a minimal seed — actual compaction depends on token counting
4+
-- which requires the LLM provider. This just ensures the DB structure is valid.
5+
6+
INSERT OR IGNORE INTO session (id, project_id, directory, title, version, time_created, time_updated)
7+
VALUES ('ses_sanity_compaction_test', 'proj_sanity', '/tmp', 'Compaction Test Session', 1, strftime('%s','now') * 1000, strftime('%s','now') * 1000);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"provider": {
3+
"anthropic": {
4+
"api_key": "test-key-not-real"
5+
}
6+
},
7+
"experimental": {
8+
"auto_mcp_discovery": true
9+
}
10+
}

test/sanity/fixtures/test.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- Known anti-patterns for sql_analyze sanity testing
2+
-- SELECT * is a classic anti-pattern that should always be flagged
3+
SELECT * FROM users WHERE id IN (1, 2, 3, 4, 5);
4+
5+
-- Implicit join (cartesian product risk)
6+
SELECT u.name, o.total FROM users u, orders o WHERE u.id = o.user_id;

0 commit comments

Comments
 (0)