diff --git a/test/sanity/Dockerfile.verdaccio b/test/sanity/Dockerfile.verdaccio new file mode 100644 index 000000000..8b1e7b4de --- /dev/null +++ b/test/sanity/Dockerfile.verdaccio @@ -0,0 +1,42 @@ +FROM oven/bun:1.3.10-debian + +# System deps (what a real user would have) +RUN apt-get update && apt-get install -y \ + git python3 python3-pip python3-venv curl sqlite3 npm \ + && rm -rf /var/lib/apt/lists/* + +# dbt in venv (matches real user setup) +RUN python3 -m venv /opt/dbt && \ + /opt/dbt/bin/pip install --quiet dbt-core dbt-duckdb dbt-postgres +ENV PATH="/opt/dbt/bin:$PATH" + +# Fresh user, clean HOME — simulates new user +ENV HOME=/home/testuser +RUN useradd -m testuser +USER testuser +WORKDIR /home/testuser + +# ── Copy build artifacts (pre-built by `bun run build`) ──────── +COPY --chown=testuser packages/opencode/dist/ /home/testuser/build/packages/opencode/dist/ +COPY --chown=testuser packages/opencode/bin/ /home/testuser/build/packages/opencode/bin/ +COPY --chown=testuser packages/opencode/script/postinstall.mjs /home/testuser/build/packages/opencode/script/postinstall.mjs +COPY --chown=testuser packages/opencode/package.json /home/testuser/build/packages/opencode/package.json +COPY --chown=testuser packages/dbt-tools/dist/ /home/testuser/build/packages/dbt-tools/dist/ +COPY --chown=testuser packages/dbt-tools/bin/ /home/testuser/build/packages/dbt-tools/bin/ +COPY --chown=testuser .opencode/skills/ /home/testuser/build/.opencode/skills/ +COPY --chown=testuser LICENSE /home/testuser/build/LICENSE +COPY --chown=testuser CHANGELOG.md /home/testuser/build/CHANGELOG.md +COPY --chown=testuser README.md /home/testuser/build/README.md + +# Copy test scripts and entrypoint +COPY --chown=testuser test/sanity/ /home/testuser/sanity/ +RUN chmod +x /home/testuser/sanity/run.sh \ + /home/testuser/sanity/phases/*.sh \ + /home/testuser/sanity/pr-tests/*.sh \ + /home/testuser/sanity/lib/*.sh + +# Copy the publish-and-install entrypoint +COPY --chown=testuser test/sanity/verdaccio/entrypoint.sh /home/testuser/entrypoint.sh +RUN chmod +x /home/testuser/entrypoint.sh + +ENTRYPOINT ["/home/testuser/entrypoint.sh"] diff --git a/test/sanity/docker-compose.verdaccio.yml b/test/sanity/docker-compose.verdaccio.yml new file mode 100644 index 000000000..7524a8526 --- /dev/null +++ b/test/sanity/docker-compose.verdaccio.yml @@ -0,0 +1,55 @@ +# Verdaccio-based sanity suite: tests the real npm install -g flow +# Usage: docker compose -f test/sanity/docker-compose.verdaccio.yml up --build --abort-on-container-exit --exit-code-from sanity +services: + verdaccio: + image: verdaccio/verdaccio + volumes: + - ./verdaccio/config.yaml:/verdaccio/conf/config.yaml + ports: + - "127.0.0.1:4873:4873" + healthcheck: + test: ["CMD", "wget", "-q", "-O/dev/null", "http://0.0.0.0:4873/-/ping"] + interval: 3s + retries: 10 + + sanity: + build: + context: ../.. + dockerfile: test/sanity/Dockerfile.verdaccio + args: + REGISTRY_URL: http://verdaccio:4873 + environment: + - ANTHROPIC_API_KEY + - ALTIMATE_CODE_CONN_SNOWFLAKE_TEST + - TEST_PG_HOST=postgres + - TEST_PG_PORT=5432 + - TEST_PG_PASSWORD=testpass123 + - TEST_MONGODB_HOST=mongodb + - TEST_MONGODB_PORT=27017 + depends_on: + verdaccio: + condition: service_healthy + postgres: + condition: service_healthy + mongodb: + condition: service_healthy + + postgres: + image: postgres:16-alpine + environment: + POSTGRES_PASSWORD: testpass123 + ports: + - "127.0.0.1:15432:5432" + healthcheck: + test: pg_isready + interval: 5s + retries: 10 + + mongodb: + image: mongo:7 + ports: + - "127.0.0.1:17017:27017" + healthcheck: + test: mongosh --eval "db.adminCommand('ping')" --quiet + interval: 5s + retries: 10 diff --git a/test/sanity/run-verdaccio.sh b/test/sanity/run-verdaccio.sh new file mode 100755 index 000000000..0af81e535 --- /dev/null +++ b/test/sanity/run-verdaccio.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# Run the Verdaccio-based sanity suite: build → publish to local registry → npm install -g → test +# This tests the EXACT same flow a real user goes through with `npm install -g altimate-code` +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +echo "========================================" +echo " Verdaccio Sanity Suite" +echo " Tests the real npm install -g flow" +echo " Time: $(date -u +%Y-%m-%dT%H:%M:%SZ)" +echo "========================================" +echo "" + +# Ensure binary is built +if [ ! -d "$REPO_ROOT/packages/opencode/dist/@altimateai" ]; then + echo "ERROR: No built binary found at packages/opencode/dist/@altimateai/" + echo " Run: cd packages/opencode && bun run build" + exit 1 +fi + +# Clean up on exit +cleanup() { + echo "" + echo "Cleaning up Docker containers..." + docker compose -f "$SCRIPT_DIR/docker-compose.verdaccio.yml" down --volumes --remove-orphans 2>/dev/null || true +} +trap cleanup EXIT + +# Run the suite (disable errexit so we capture the exit code for summary) +set +e +docker compose -f "$SCRIPT_DIR/docker-compose.verdaccio.yml" up \ + --build \ + --abort-on-container-exit \ + --exit-code-from sanity +EXIT_CODE=$? +set -e + +echo "" +if [ $EXIT_CODE -eq 0 ]; then + echo " VERDACCIO SANITY: ALL PASSED" +else + echo " VERDACCIO SANITY: FAILED (exit $EXIT_CODE)" +fi + +exit $EXIT_CODE diff --git a/test/sanity/verdaccio/config.yaml b/test/sanity/verdaccio/config.yaml new file mode 100644 index 000000000..4d3544d9b --- /dev/null +++ b/test/sanity/verdaccio/config.yaml @@ -0,0 +1,29 @@ +# Verdaccio config for sanity testing — fully open, no auth required +storage: /verdaccio/storage +auth: + htpasswd: + file: /verdaccio/conf/htpasswd + max_users: -1 +uplinks: + npmjs: + url: https://registry.npmjs.org/ +packages: + "@altimateai/*": + access: $all + publish: $all + unpublish: $all + # no proxy: fail fast if the package was not published locally + "altimate-code": + access: $all + publish: $all + unpublish: $all + "**": + access: $all + proxy: npmjs +max_body_size: 500mb +server: + keepAliveTimeout: 60 +log: + type: stdout + format: pretty + level: warn diff --git a/test/sanity/verdaccio/entrypoint.sh b/test/sanity/verdaccio/entrypoint.sh new file mode 100755 index 000000000..e801edb0a --- /dev/null +++ b/test/sanity/verdaccio/entrypoint.sh @@ -0,0 +1,137 @@ +#!/bin/bash +# Verdaccio sanity entrypoint: publish to local registry → npm install -g → run sanity tests +# This runs at container startup (not during build) so Verdaccio is available. +set -euo pipefail + +REGISTRY_URL="${REGISTRY_URL:-http://verdaccio:4873}" +BUILD_DIR="/home/testuser/build" + +echo "========================================" +echo " Verdaccio Install Pipeline" +echo " Registry: $REGISTRY_URL" +echo "========================================" + +# ── Wait for Verdaccio ───────────────────────────────────────── +echo "" +echo "--- Waiting for Verdaccio ---" +for i in $(seq 1 30); do + if curl -sf "$REGISTRY_URL/-/ping" >/dev/null 2>&1; then + echo " Verdaccio is ready" + break + fi + if [ "$i" -eq 30 ]; then + echo " FATAL: Verdaccio not reachable after 30s" + exit 1 + fi + sleep 1 +done + +# ── Configure npm to use Verdaccio ───────────────────────────── +# Write .npmrc directly — npm config set doesn't always work for scoped registries +REGISTRY_HOST="${REGISTRY_URL#http://}" +REGISTRY_HOST="${REGISTRY_HOST#https://}" +REGISTRY_HOST="${REGISTRY_HOST%/}" +cat > ~/.npmrc <&1 + +# ── Step 2: Build and publish main altimate-code package ─────── +echo "" +echo "--- Building main package ---" +PUBLISH_DIR=$(mktemp -d /tmp/altimate-publish-XXXXXX) + +# Copy exactly what publish.ts copies +cd "$BUILD_DIR/packages/opencode" +cp -r bin "$PUBLISH_DIR/bin" +cp script/postinstall.mjs "$PUBLISH_DIR/postinstall.mjs" +cp -r "$BUILD_DIR/.opencode/skills" "$PUBLISH_DIR/skills" + +# Bundle dbt-tools +mkdir -p "$PUBLISH_DIR/dbt-tools/bin" "$PUBLISH_DIR/dbt-tools/dist" +cp "$BUILD_DIR/packages/dbt-tools/bin/altimate-dbt" "$PUBLISH_DIR/dbt-tools/bin/altimate-dbt" +cp "$BUILD_DIR/packages/dbt-tools/dist/index.js" "$PUBLISH_DIR/dbt-tools/dist/" +cp "$BUILD_DIR/packages/dbt-tools/dist/node_python_bridge.py" "$PUBLISH_DIR/dbt-tools/dist/" +echo '{"type":"module"}' > "$PUBLISH_DIR/dbt-tools/package.json" + +cp "$BUILD_DIR/LICENSE" "$PUBLISH_DIR/LICENSE" +cp "$BUILD_DIR/CHANGELOG.md" "$PUBLISH_DIR/CHANGELOG.md" + +# Write package.json matching what publish.ts produces +node -e " +const pkg = { + name: 'altimate-code', + version: '$VERSION', + license: 'MIT', + bin: { altimate: './bin/altimate', 'altimate-code': './bin/altimate-code' }, + scripts: { postinstall: 'bun ./postinstall.mjs || node ./postinstall.mjs' }, + dependencies: { '@altimateai/altimate-core': '$CORE_DEP' }, + optionalDependencies: { '$BIN_NAME': '$BIN_VERSION' }, + peerDependencies: { + pg: '>=8', 'snowflake-sdk': '>=1', '@google-cloud/bigquery': '>=8', + '@databricks/sql': '>=1', mysql2: '>=3', mssql: '>=11', + oracledb: '>=6', duckdb: '>=1', mongodb: '>=6' + }, + peerDependenciesMeta: { + pg: {optional:true}, 'snowflake-sdk': {optional:true}, + '@google-cloud/bigquery': {optional:true}, '@databricks/sql': {optional:true}, + mysql2: {optional:true}, mssql: {optional:true}, oracledb: {optional:true}, + duckdb: {optional:true}, mongodb: {optional:true} + } +}; +require('fs').writeFileSync('$PUBLISH_DIR/package.json', JSON.stringify(pkg,null,2)+'\n'); +" + +echo "--- Publishing main package ---" +cd "$PUBLISH_DIR" +npm publish --registry "$REGISTRY_URL" 2>&1 +echo " Published altimate-code@$VERSION to $REGISTRY_URL" + +# ── Step 3: Clean install as a real user ─────────────────────── +echo "" +echo "--- npm install -g altimate-code ---" +# Wipe build artifacts — user starts fresh +rm -rf "$BUILD_DIR" "$PUBLISH_DIR" +cd /home/testuser + +# Install to user-local prefix (testuser can't write /usr/local) +mkdir -p /home/testuser/.npm-global +npm config set prefix /home/testuser/.npm-global +export PATH="/home/testuser/.npm-global/bin:$PATH" +npm install -g altimate-code --registry "$REGISTRY_URL" 2>&1 +echo "" +echo " Installed: $(which altimate 2>/dev/null || echo 'NOT FOUND')" +echo " Version: $(altimate --version 2>/dev/null || echo 'FAILED')" + +# ── Step 4: Run sanity tests ────────────────────────────────── +echo "" +echo "========================================" +echo " Running sanity tests against npm install" +echo "========================================" +exec /home/testuser/sanity/run.sh diff --git a/test/sanity/verdaccio/htpasswd b/test/sanity/verdaccio/htpasswd new file mode 100644 index 000000000..e7fb46b01 --- /dev/null +++ b/test/sanity/verdaccio/htpasswd @@ -0,0 +1 @@ +sanity:{SHA}uwvl+vY+h0Xh0mhJ0H5e7QVlrQg=