From 487444b8d61cf3fb04796efb6d641b1dbeb0e6c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Tue, 26 May 2026 18:35:38 +0100 Subject: [PATCH] test(cli): add command-coverage smoke gate scripts/cli-smoke.sh boots one local lobu run (embedded Postgres + the deterministic mock provider from scripts/sdk-e2e/) under an isolated HOME and walks every lobu command/subcommand once, asserting each runs or fails gracefully. Complements sdk-e2e.sh (SDK lifecycle) with breadth across the whole command surface: init/validate/doctor/telemetry, context/org/token, agent CRUD, call, chat (json/new/dry-run/continue), apply, link, memory (health/run/exec/ seed/configure/init/org), login/logout, connector. Browser/TTY/external-PG paths are exercised at the runs-gracefully level or skipped with a reason. Wired as the cli-smoke CI job (mirrors sdk-e2e) and make test-e2e-cli. Biome ignores the .cli-smoke-run/.sdk-e2e-run/.managed-e2e-run scratch dirs. --- .github/workflows/ci.yml | 46 +++++ .gitignore | 1 + Makefile | 10 +- config/biome.config.json | 3 + scripts/cli-smoke.sh | 375 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 434 insertions(+), 1 deletion(-) create mode 100755 scripts/cli-smoke.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 142f61bb9..7679a6cd8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,6 +155,52 @@ jobs: - name: SDK lifecycle e2e (apply + prune + real worker turn) run: bash scripts/sdk-e2e.sh + # CLI command-coverage smoke — the hard gate that EVERY `lobu` command runs, + # not just the SDK lifecycle (sdk-e2e covers that). Boots one `lobu run` + # (embedded Postgres + a deterministic mock provider, no key) under an isolated + # HOME and walks the whole command surface, asserting each runs or fails + # gracefully. A failure here fails CI → the PR goes red. See scripts/cli-smoke.sh. + cli-smoke: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + - uses: ./.github/actions/setup-submodule + with: + deploy-key: ${{ secrets.OWLETTO_WEB_DEPLOY_KEY }} + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.5 + + # Node 22 — the worker uses isolated-vm (abi127 prebuild); the runner's + # default Node major would segfault the sandbox runtime. + - uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Install dependencies + run: bun install + + # The worker's exec-sandbox uses bwrap on Linux; ubuntu-latest blocks + # unprivileged user namespaces via AppArmor, so flip the sysctl too. + - name: Install bubblewrap (worker exec-sandbox) + run: | + sudo apt-get update + sudo apt-get install -y bubblewrap coreutils + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + bwrap --version + + # Embedded Postgres (PG18) ICU shim — see the sdk-e2e job note above; + # scripts/cli-smoke.sh runs the same scripts/sdk-e2e/fix-embedded-pg-icu.mjs. + + - name: Build all packages (CLI + server bundle + sdk + pgvector-embedded) + run: make build-packages + + - name: CLI command-coverage smoke (every command runs) + run: bash scripts/cli-smoke.sh + # Frontend tests run under jsdom via vitest. owletto is a submodule; # forks without the deploy key get a stub package and skip these. frontend: diff --git a/.gitignore b/.gitignore index 14e71a6e9..35263f23d 100644 --- a/.gitignore +++ b/.gitignore @@ -97,3 +97,4 @@ packages/*/~/ .lobu-dev/ .sdk-e2e-run/ .managed-e2e-run/ +.cli-smoke-run/ diff --git a/Makefile b/Makefile index 9ef8e4959..01c6968a9 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Development Makefile for Lobu -.PHONY: help setup build test clean dev build-packages ensure-submodule clean-workers test-unit test-integration test-e2e test-e2e-sdk typecheck task-setup task-clean e2e-browser bump review +.PHONY: help setup build test clean dev build-packages ensure-submodule clean-workers test-unit test-integration test-e2e test-e2e-sdk test-e2e-cli typecheck task-setup task-clean e2e-browser bump review # Default target help: @@ -12,6 +12,7 @@ help: @echo " make test-unit - Run the CI unit suite (no Postgres needed)" @echo " make test-integration - Run the CI integration suite (needs DATABASE_URL with pgvector)" @echo " make test-e2e - Boot the dev server + run openclaw-plugin e2e against it" + @echo " make test-e2e-cli - Boot lobu run + walk every CLI command (the CI cli-smoke gate)" @echo " make clean-workers - Stop any running embedded worker subprocesses" @echo " make typecheck - Strict typecheck (same as Dockerfile) for server + owletto" @echo " make task-setup NAME= - Create a paired worktree at .claude/worktrees/ (lobu + submodule on real branch, .env copied, ports auto-assigned, Lobu context registered)" @@ -148,6 +149,13 @@ test-e2e: test-e2e-sdk: @./scripts/sdk-e2e.sh +# CLI command-coverage smoke: boots one `lobu run` (embedded Postgres + mock +# provider) under an isolated HOME and walks EVERY `lobu` command/subcommand +# once, asserting each runs (or fails gracefully). Self-contained, no key. This +# is the CI `cli-smoke` gate; run it locally the same way. +test-e2e-cli: + @./scripts/cli-smoke.sh + # Stop any embedded worker subprocesses left over from a crashed gateway. # Workers are normally cleaned up when the gateway exits; this target is a # safety net for orphaned bun processes spawned by EmbeddedDeploymentManager. diff --git a/config/biome.config.json b/config/biome.config.json index cf42bfeaf..adbca8587 100644 --- a/config/biome.config.json +++ b/config/biome.config.json @@ -14,6 +14,9 @@ "!**/workspaces/**", "!**/.claude/debug/**", "!**/.claude/worktrees/**", + "!**/.cli-smoke-run/**", + "!**/.sdk-e2e-run/**", + "!**/.managed-e2e-run/**", "!**/dist/**", "!**/src/errors.js", "!**/*.raw.js", diff --git a/scripts/cli-smoke.sh b/scripts/cli-smoke.sh new file mode 100755 index 000000000..cfc2712b9 --- /dev/null +++ b/scripts/cli-smoke.sh @@ -0,0 +1,375 @@ +#!/usr/bin/env bash +# +# CLI command-coverage smoke gate. +# +# Where scripts/sdk-e2e.sh proves the SDK *lifecycle* (apply -> prune -> worker +# turn -> connector -> watcher -> client), THIS gate proves that EVERY `lobu` +# command/subcommand actually RUNS -- argv parses, the handler executes, and a +# representative invocation returns the documented success marker (or, for the +# negative cases, fails gracefully with the documented message instead of a +# crash/stack trace). +# +# It boots ONE local `lobu run` (embedded Postgres + a deterministic mock +# OpenAI-compatible provider, reusing the scripts/sdk-e2e/ harness -- no provider +# key, reproducible in CI) under an ISOLATED $HOME, then walks the whole command +# surface. Unlike sdk-e2e it does NOT fail-fast: it records every miss and exits +# non-zero at the end with a summary, so one run tells you exactly which commands +# are broken. +# +# Commands that genuinely need a browser, a real TTY, an external Postgres, or a +# configured chat platform can't be driven unattended -- those are exercised at +# the "runs + fails gracefully" level or logged as SKIP with the reason. +# +# Kept ASCII-only on purpose: a stray non-ASCII byte hugging a $var expansion is +# swallowed into the variable name under a UTF-8 locale ("unbound variable"). +# +# Usage: scripts/cli-smoke.sh (embedded Postgres, the default) +# DATABASE_URL=... scripts/cli-smoke.sh (external Postgres; also exercises `token revoke`) +set -uo pipefail + +WT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +HARNESS="$WT/scripts/sdk-e2e" # reuse the deterministic provider + ICU fix +LOBU_BIN="$WT/packages/cli/bin/lobu.js" +GW_PORT="${GW_PORT:-8795}" +MOCK_PORT="${MOCK_PORT:-11436}" +MOCK_REPLY="CLI_SMOKE_OK" +RUN_DIR="$WT/.cli-smoke-run" +RUN_LOG="$RUN_DIR/run.log" +MOCK_LOG="$RUN_DIR/mock.log" +OUT="$RUN_DIR/cmd.out" # scratch for the most-recent command + +# Node 22-24 is required (the worker uses isolated-vm). Prefer a Homebrew +# node@22 locally; CI provides node via actions/setup-node. +if [ -x /opt/homebrew/opt/node@22/bin/node ] && ! node --version 2>/dev/null | grep -qE '^v(22|23|24)\.'; then + export PATH="/opt/homebrew/opt/node@22/bin:$PATH" +fi + +# Isolate ALL global CLI state under a throwaway HOME. The CLI reads/writes +# ~/.config/lobu/{config,credentials,threads}.json, ~/.openclaw/openclaw.json, +# and the embedded Postgres data dir defaults to ~/.lobu/pgdata. Overriding HOME +# contains every one of those in $RUN_DIR so the gate never clobbers the dev's +# real contexts/login (and gives a fresh DB each run). +export HOME="$RUN_DIR/home" + +MOCK_PID="" +cleanup() { + [ -n "$MOCK_PID" ] && kill -9 "$MOCK_PID" 2>/dev/null || true + lsof -nP -iTCP:"$GW_PORT" -sTCP:LISTEN -t 2>/dev/null | xargs -r kill -9 2>/dev/null || true + lsof -nP -iTCP:"$MOCK_PORT" -sTCP:LISTEN -t 2>/dev/null | xargs -r kill -9 2>/dev/null || true +} +trap cleanup EXIT + +# ---- Reporting -------------------------------------------------------------- +PASSES=0; FAILS=0; SKIPS=0 +pass() { echo " [OK] $*"; PASSES=$((PASSES + 1)); } +skip() { echo " [SKIP] $*"; SKIPS=$((SKIPS + 1)); } +note() { echo ""; echo "== $* =="; } +softfail() { + echo " [FAIL] $*" >&2 + echo " -- last 12 lines of output --" >&2 + tail -12 "$OUT" 2>/dev/null | sed 's/^/ /' >&2 + FAILS=$((FAILS + 1)) +} +# Hard failure for setup prerequisites -- no point walking commands if the +# server never came up. +die() { echo "ABORT: CLI smoke -- $*" >&2; [ -f "$RUN_LOG" ] && { echo "--- last 40 lines of run.log ---" >&2; tail -40 "$RUN_LOG" >&2; }; exit 1; } + +# Run `lobu ` in , capture combined output to $OUT, set RC to the +# CLI's exit code. errexit is disabled; callers inspect RC explicitly. +RC=0 +runlobu() { + local cwd="$1"; shift + ( cd "$cwd" && node "$LOBU_BIN" "$@" ) > "$OUT" 2>&1 -> exit 0 AND present +expect_grep() { + local desc="$1" marker="$2" cwd="$3"; shift 3 + runlobu "$cwd" "$@" + if [ "$RC" -eq 0 ] && grep -qF -- "$marker" "$OUT"; then pass "$desc" + else softfail "$desc (exit=$RC, missing marker: $marker) [lobu $*]"; fi +} + +# expect_ok -> exit 0 (no marker) +expect_ok() { + local desc="$1" cwd="$2"; shift 2 + runlobu "$cwd" "$@" + if [ "$RC" -eq 0 ]; then pass "$desc" + else softfail "$desc (exit=$RC) [lobu $*]"; fi +} + +# expect_fail_grep -> graceful failure: +# non-zero exit AND the documented error message present (no crash/stack trace). +expect_fail_grep() { + local desc="$1" marker="$2" cwd="$3"; shift 3 + runlobu "$cwd" "$@" + if [ "$RC" -ne 0 ] && grep -qiF -- "$marker" "$OUT"; then pass "$desc (graceful: $marker)" + else softfail "$desc (expected non-zero exit + '$marker', got exit=$RC) [lobu $*]"; fi +} + +# expect_exit -> exact exit code +expect_exit() { + local desc="$1" code="$2" cwd="$3"; shift 3 + runlobu "$cwd" "$@" + if [ "$RC" -eq "$code" ]; then pass "$desc (exit $code)" + else softfail "$desc (expected exit $code, got $RC) [lobu $*]"; fi +} + +echo ">> node $(node --version), gateway :$GW_PORT, mock :$MOCK_PORT, HOME=$HOME" +rm -rf "$RUN_DIR"; mkdir -p "$RUN_DIR" "$HOME" +cleanup # free ports from any prior run + +# 0) Embedded-PG ICU shims on Linux (no-op on macOS). See sdk-e2e.sh step 0. +if [ -z "${DATABASE_URL:-}" ]; then + node "$HARNESS/fix-embedded-pg-icu.mjs" || die "could not prepare embedded-postgres ICU symlinks" +fi + +# 1) Deterministic mock OpenAI-compatible provider. +MOCK_PORT="$MOCK_PORT" MOCK_REPLY="$MOCK_REPLY" node "$HARNESS/mock-openai.mjs" > "$MOCK_LOG" 2>&1 & +MOCK_PID=$! +disown "$MOCK_PID" 2>/dev/null || true +for _ in $(seq 1 20); do + curl -fsS -X POST "http://127.0.0.1:$MOCK_PORT/v1/chat/completions" -H 'content-type: application/json' -d '{}' >/dev/null 2>&1 && break + sleep 0.5 +done +curl -fsS -X POST "http://127.0.0.1:$MOCK_PORT/v1/chat/completions" -H 'content-type: application/json' -d '{}' >/dev/null 2>&1 || die "mock server did not come up" + +# The mock provider lives on $MOCK_PORT, but the shared harness providers.json +# hardcodes 11434. Rewrite a copy so the registry points at our port. +PROVIDERS="$RUN_DIR/providers.json" +node -e 'const fs=require("fs");const j=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));const port=process.argv[2];for(const grp of j.providers||[])for(const sub of grp.providers||[])if(sub.upstreamBaseUrl)sub.upstreamBaseUrl=sub.upstreamBaseUrl.replace(/:\d+/,":"+port);fs.writeFileSync(process.argv[3],JSON.stringify(j,null,2))' "$HARNESS/providers.json" "$MOCK_PORT" "$PROVIDERS" +export LOBU_PROVIDER_REGISTRY_PATH="$PROVIDERS" + +# ============================================================================ +# STATIC COMMANDS (no server needed) +# ============================================================================ +note "top-level" +VERSION="$(node "$LOBU_BIN" --version 2>/dev/null | tr -d '[:space:]')" +expect_grep "lobu --version" "$VERSION" "$WT" --version +expect_grep "lobu --help" "CLI for deploying and managing AI agents on Lobu" "$WT" --help +expect_exit "lobu -> usage error" 1 "$WT" definitely-not-a-command + +note "init / validate / doctor / telemetry / agent scaffold (static)" +expect_grep "lobu init --list-providers" "--provider" "$WT" init --list-providers + +# Scaffold the project we'll boot. Mirror sdk-e2e: scaffold, drop package.json +# so jiti resolves the workspace @lobu/cli/config, then overwrite the config to +# point the agent at the deterministic mock provider. +PROJ="$RUN_DIR/proj"; mkdir -p "$PROJ" +expect_grep "lobu init . --here" "Lobu initialized" "$PROJ" init . -y --here --provider gemini +rm -f "$PROJ/package.json" +cat > "$PROJ/lobu.config.ts" <<'TS' +import { defineAgent, defineConfig, defineEntityType, secret } from "@lobu/cli/config"; + +const echo = defineAgent({ + id: "echo", name: "Echo", dir: "./agents/echo", + providers: [{ id: "mock", model: "mock-model", key: secret("MOCK_API_KEY") }], +}); +const note = defineEntityType({ key: "note", name: "Note" }); + +export default defineConfig({ agents: [echo], entities: [note] }); +TS + +expect_grep "lobu validate" "is valid" "$PROJ" validate + +# Negative: a syntactically broken config must be rejected (non-zero), not crash. +BADPROJ="$RUN_DIR/badproj"; mkdir -p "$BADPROJ" +printf 'import { defineConfig } from "@lobu/cli/config";\nexport default defineConfig({ agents: [ }\n' > "$BADPROJ/lobu.config.ts" +expect_exit "lobu validate (broken config -> non-zero)" 1 "$BADPROJ" validate + +# doctor's DB line is backend-specific: embedded mode prints "embedded +# Postgres"; an external DATABASE_URL connects for real and prints no such +# marker. Gate the embedded assertion on the mode (mirrors sdk-e2e.sh); always +# assert there's no spurious connect failure. +runlobu "$PROJ" doctor +if grep -qiE "connect failed|ENOTFOUND" "$OUT"; then + softfail "lobu doctor false-failed the DB check (lobu doctor)" +elif [ -z "${DATABASE_URL:-}" ] && ! grep -qF "embedded Postgres" "$OUT"; then + softfail "lobu doctor did not recognize the embedded Postgres backend" +else + pass "lobu doctor (DB check healthy)" +fi + +expect_grep "lobu telemetry status" "Telemetry:" "$PROJ" telemetry status +expect_grep "lobu telemetry on" "Telemetry enabled" "$PROJ" telemetry on +expect_grep "lobu telemetry status (now on)" "Telemetry: on" "$PROJ" telemetry status +expect_grep "lobu telemetry off" "Telemetry disabled" "$PROJ" telemetry off + +expect_grep "lobu agent scaffold (local)" "Scaffolded agent" "$PROJ" agent scaffold helper --name Helper +expect_fail_grep "lobu agent scaffold (dup -> graceful)" "already exists" "$PROJ" agent scaffold helper + +note "context (local config CRUD)" +expect_grep "lobu context list" "contexts" "$PROJ" context list +expect_grep "lobu context current" "context" "$PROJ" context current +expect_grep "lobu context add" "Saved context" "$PROJ" context add smoke-ctx --url "http://localhost:$GW_PORT" +expect_grep "lobu context use" "Switched to context smoke-ctx" "$PROJ" context use smoke-ctx +expect_grep "lobu context rm" "Removed context smoke-ctx" "$PROJ" context rm smoke-ctx + +note "connector runtime-self-check (CI smoke gate, no server)" +expect_ok "lobu connector runtime-self-check --json" "$PROJ" connector runtime-self-check --json + +note "apply --only validation (no server)" +expect_exit "lobu apply --only bogus -> exit 2" 2 "$PROJ" apply --only bogus + +# ============================================================================ +# Boot the embedded stack (auto-applies the project -> registers `local` ctx) +# ============================================================================ +note "boot: lobu run --port $GW_PORT" +{ + printf '\n' + echo "MOCK_API_KEY=mock-key-smoke" + echo "WORKER_ALLOWED_DOMAINS=127.0.0.1,localhost" + echo "LOBU_DISABLE_SYSTEMD_RUN=1" + [ -n "${DATABASE_URL:-}" ] && echo "DATABASE_URL=$DATABASE_URL" +} >> "$PROJ/.env" + +( cd "$PROJ" && node "$LOBU_BIN" run --port "$GW_PORT" > "$RUN_LOG" 2>&1 ) & +# Wait on the auto-apply markers only -- "api docs:" prints BEFORE the project +# auto-applies, so breaking on it would race the apply (see sdk-e2e.sh). +for _ in $(seq 1 120); do + grep -qiE "Apply complete|auto-apply skipped|Apply halted" "$RUN_LOG" 2>/dev/null && break + sleep 1 +done +grep -qi "Apply complete" "$RUN_LOG" || die "lobu run did not auto-apply (skipped/halted?)" +pass "lobu run booted + auto-applied the project" + +# Trigger loopback auth (local-init) + resolve the bootstrap org slug. +runlobu "$PROJ" whoami -c local +ORG="$( ( cd "$PROJ" && node "$LOBU_BIN" org current -c local 2>/dev/null ) | grep -oE '[a-z0-9][a-z0-9-]*' | grep -vE '^local$|^org$|^for$|^context$|^current$|^no$|^active$|^set$' | tail -1 )" +[ -n "$ORG" ] || die "could not resolve the local org slug (lobu org current -c local)" +echo ">> resolved local org: $ORG" + +# ============================================================================ +# SERVER-BACKED COMMANDS (loopback `local` context, auto-authed via local-init) +# ============================================================================ +note "identity / status / token" +expect_grep "lobu whoami -c local" "Context" "$PROJ" whoami -c local +expect_grep "lobu status -c local" "API:" "$PROJ" status -c local +expect_grep "lobu token -c local" "Token" "$PROJ" token -c local +runlobu "$PROJ" token -c local --raw +{ [ "$RC" -eq 0 ] && [ -s "$OUT" ]; } && pass "lobu token --raw (non-empty)" || softfail "lobu token --raw produced no token (exit=$RC)" +expect_grep "lobu token create -c local" "created" "$PROJ" token create -c local --scope "mcp:read mcp:write" --name smoke-token + +# token revoke needs an EXTERNAL Postgres (direct INSERT into revoked_tokens). +# In embedded mode the shell DATABASE_URL is unset -> assert the documented +# guard fires gracefully; with an external DATABASE_URL, do a real revoke. +if [ -n "${DATABASE_URL:-}" ] && [[ "${DATABASE_URL:-}" == postgres* ]]; then + JTI="$( ( cd "$PROJ" && node "$LOBU_BIN" token create -c local --scope "mcp:read" --json 2>/dev/null ) | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const j=JSON.parse(s);process.stdout.write(j.jti||j.id||"")}catch{}})' )" + if [ -n "$JTI" ]; then expect_grep "lobu token revoke (external PG)" "revoked" "$PROJ" token revoke "$JTI" + else skip "lobu token revoke -- token create --json exposed no jti"; fi +else + ( cd "$PROJ" && env -u DATABASE_URL node "$LOBU_BIN" token revoke smoke-jti ) > "$OUT" 2>&1 graceful)" "at least one" "$PROJ" agent update smoke-agent -c local +# config get --output, then round-trip that config back through patch (always valid). +expect_grep "lobu agent config get --output -c local" "Wrote" "$PROJ" agent config get smoke-agent -c local --output "$RUN_DIR/agent-config.json" +expect_grep "lobu agent config patch -c local" "Updated config" "$PROJ" agent config patch smoke-agent -c local --file "$RUN_DIR/agent-config.json" +expect_fail_grep "lobu agent delete (no --yes -> graceful)" "Refusing to delete" "$PROJ" agent delete smoke-agent -c local +expect_grep "lobu agent delete --yes -c local" "Deleted agent" "$PROJ" agent delete smoke-agent -c local --yes + +note "call (generic admin REST dispatcher)" +expect_grep "lobu call --list -c local" "tool(s)" "$PROJ" call --list -c local +expect_ok "lobu call manage_feeds list_feeds -c local" "$PROJ" call manage_feeds -c local --arg "action=list_feeds" + +note "init --from-org (bootstrap a re-appliable project from a live org)" +# init takes no -c flag; it uses the active context (local-init switched it to +# `local`) + --url to pin the server. Scaffolds into $RUN_DIR/fromorg. +rm -rf "$RUN_DIR/fromorg" +( cd "$RUN_DIR" && node "$LOBU_BIN" init fromorg -y --from-org "$ORG" --url "http://localhost:$GW_PORT" ) > "$OUT" 2>&1 "$OUT" 2>&1 "$OUT" 2>&1 "$OUT" 2>&1 "$OUT" 2>&1 "$OUT" 2>&1 "$OUT" 2>&1 "$SEEDPROJ/lobu.config.ts" < "cli-smoke-exec-ok";' > "$RUN_DIR/exec.ts" +expect_ok "lobu memory exec (ClientSDK script)" "$PROJ" memory exec "$RUN_DIR/exec.ts" -c local +# memory init -- wire a local MCP CLIENT (the --agent is a coding tool like +# openclaw/claude-code, NOT a Lobu agent id) to the memory MCP url. --url skips +# the picker; --skip-auth skips the login step; isolated HOME contains the write. +( cd "$PROJ" && timeout 30 node "$LOBU_BIN" memory init --url "http://localhost:$GW_PORT/mcp" --agent openclaw --skip-auth ) > "$OUT" 2>&1 /dev/null ) | tr -d '[:space:]' )" +expect_grep "lobu context add (for login)" "Saved context" "$PROJ" context add smoke-login --url "http://localhost:$GW_PORT" +if [ -n "$PAT" ]; then + expect_grep "lobu login --token" "Logged in" "$PROJ" login --token "$PAT" -c smoke-login +else + skip "lobu login --token -- could not mint a PAT to log in with" +fi +expect_grep "lobu logout -c smoke-login" "Logged out" "$PROJ" logout -c smoke-login +expect_grep "lobu context rm smoke-login" "Removed context" "$PROJ" context rm smoke-login +# Device-code login on a fresh (unauthed) context with no TTY must bail cleanly +# -- the same graceful headless path `lobu login`/--quiet takes in CI. Use a +# FRESH context: `local` is already authed (local-init) and would short-circuit. +runlobu "$PROJ" context add smoke-empty --url "http://localhost:$GW_PORT" +expect_fail_grep "lobu login (non-interactive device-code -> graceful bail)" "interactive terminal" "$PROJ" login -c smoke-empty +runlobu "$PROJ" context rm smoke-empty + +note "connector run (needs Chrome/Playwright + a browser_session profile)" +# --check resolves+validates without executing; with no auth profile it errors -- +# assert it fails gracefully (clean message, controlled exit), not a crash. +runlobu "$PROJ" connector run --check -c local +[ "$RC" -ne 0 ] && pass "lobu connector run --check (graceful failure without auth profile)" || softfail "lobu connector run --check unexpectedly succeeded (exit=$RC)" + +note "browser/interactive paths -- not unattended-runnable" +skip "lobu login (interactive device-code happy path) -- needs a real TTY; --token + non-interactive bail covered above" +skip "lobu org create -- opens a browser to /orgs/new" +skip "lobu memory browser-auth (capture) -- needs local Chrome + Keychain prompt" +skip "lobu chat --user platform:id -- needs a configured Telegram/Slack connection" + +# ============================================================================ +echo "" +echo "================================================================" +echo " CLI smoke summary: $PASSES passed, $FAILS failed, $SKIPS skipped" +echo "================================================================" +if [ "$FAILS" -gt 0 ]; then + echo "RESULT: CLI smoke FAILED ($FAILS command(s) broken)"; exit 1 +fi +echo "RESULT: CLI smoke PASSED -- every runnable command works"