- field list/get、view list、preset list 接受 --table-id(位置参数之外) - record get/list 接受 --take/--skip 别名(= --page-size/--page 的原始偏移) - scripts/smoke-commands.sh: 全命令冒烟套件,一次性验证所有 CLI 命令 无幻影路由/无 CLI 语法错/无 CLI 请求形状错(80/80 通过,含 8 种触发器、 preset/secret/subscription/plugin 只读命令) - 轨迹依据: iteration-4 中 agent 猜 --table-id/--take 被拒,别名后归零
182 lines
11 KiB
Bash
Executable File
182 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Full-command smoke suite — exercise EVERY inkreach-teable CLI command against a
|
|
# throwaway sandbox base and assert none of them hit:
|
|
# 1. phantom routes (HTTP_404 / "Cannot GET|POST|PUT|PATCH|DELETE")
|
|
# 2. CLI syntax bugs ("unknown option" / "unknown command" / "Missing required")
|
|
# 3. CLI request-shape (zod "expected X, received Y" validation errors)
|
|
# Domain-level 4xx (e.g. "Invalid value for field") are NOT failures — the route
|
|
# exists and the backend answered.
|
|
#
|
|
# Usage:
|
|
# INKREACH_TEABLE_ENDPOINT=http://localhost:3003 INKREACH_TEABLE_TOKEN=<PAT> \
|
|
# bash scripts/smoke-commands.sh
|
|
# Reads the CLI's own config if env vars are absent. Creates + deletes a base
|
|
# named "cli-smoke-<timestamp>"; safe to run against production.
|
|
set -u
|
|
CLI_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
CLI="node $CLI_DIR/dist/bin.js"
|
|
|
|
EP="${INKREACH_TEABLE_ENDPOINT:-$(python3 -c "import json;print(json.load(open('$HOME/.inkreach-teable/config.json')).get('endpoint',''))" 2>/dev/null || true)}"
|
|
TOK="${INKREACH_TEABLE_TOKEN:-$(python3 -c "import json;print(json.load(open('$HOME/.inkreach-teable/config.json')).get('token',''))" 2>/dev/null || true)}"
|
|
[ -z "$EP" ] || [ -z "$TOK" ] && { echo "need INKREACH_TEABLE_ENDPOINT/TOKEN (or ~/.inkreach-teable/config.json)"; exit 2; }
|
|
export INKREACH_TEABLE_ENDPOINT="$EP" INKREACH_TEABLE_TOKEN="$TOK"
|
|
|
|
FAILPAT='Cannot (GET|POST|PUT|PATCH|DELETE)|HTTP_404|unknown option|unknown command|Missing required|expected [a-z]+, received'
|
|
|
|
PASS=0; FAIL=0; FAILED_CASES=()
|
|
smoke() { # smoke <label> <cmd...>
|
|
local label="$1"; shift
|
|
local out
|
|
out=$("$@" 2>&1)
|
|
if echo "$out" | grep -qE "$FAILPAT"; then
|
|
FAIL=$((FAIL+1)); FAILED_CASES+=("$label")
|
|
echo "FAIL $label"
|
|
echo "$out" | grep -E "$FAILPAT" | head -2 | sed 's/^/ /'
|
|
else
|
|
PASS=$((PASS+1))
|
|
[ "${SMOKE_VERBOSE:-0}" = 1 ] && echo "pass $label"
|
|
fi
|
|
}
|
|
j() { python3 -c "import sys,json;d=json.load(sys.stdin);print(eval(sys.argv[1]))" "$1"; }
|
|
|
|
# ---------- sandbox ----------
|
|
SPACE=$(curl -s -H "Authorization: Bearer $TOK" "$EP/api/space" | j "d[0]['id']")
|
|
BASE=$(curl -s -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
|
|
-X POST "$EP/api/base" -d "{\"spaceId\":\"$SPACE\",\"name\":\"cli-smoke-$(date +%s)\"}" | j "d['id']")
|
|
export INKREACH_TEABLE_BASE_ID="$BASE"
|
|
cleanup() { curl -s -o /dev/null -X DELETE -H "Authorization: Bearer $TOK" "$EP/api/base/$BASE"; }
|
|
trap cleanup EXIT
|
|
echo "sandbox base: $BASE"
|
|
|
|
# ---------- auth / base ----------
|
|
smoke "auth status" $CLI auth status
|
|
smoke "auth token list" $CLI auth token list
|
|
smoke "base list" $CLI base list
|
|
smoke "base get" $CLI base get "$BASE"
|
|
|
|
# ---------- table ----------
|
|
TBL=$($CLI table create --name smoke_t --fields \
|
|
'[{"name":"Title","type":"singleLineText"},{"name":"Status","type":"singleSelect","options":{"choices":["todo","done"]}},{"name":"Num","type":"number"}]' \
|
|
--json | j "d['id']")
|
|
smoke "table create" $CLI table get "$TBL"
|
|
smoke "table list" $CLI table list
|
|
smoke "table get" $CLI table get "$TBL"
|
|
smoke "table get --base-id" $CLI table get "$TBL" --base-id "$BASE"
|
|
smoke "table update" $CLI table update "$TBL" --name smoke_t2 --base-id "$BASE"
|
|
|
|
# ---------- field ----------
|
|
smoke "field list (positional)" $CLI field list "$TBL"
|
|
smoke "field list --table-id" $CLI field list --table-id "$TBL"
|
|
smoke "field list --with-options" $CLI field list "$TBL" --with-options
|
|
smoke "field get (name)" $CLI field get "$TBL" Status
|
|
smoke "field get --table-id" $CLI field get --table-id "$TBL" Status
|
|
smoke "field create" $CLI field create --table-id "$TBL" --name Extra --type singleLineText
|
|
smoke "field update --options" $CLI field update "$TBL" Status --options '{"choices":["todo","done","archived"]}'
|
|
smoke "field delete" $CLI field delete "$TBL" Extra
|
|
|
|
# ---------- record ----------
|
|
smoke "record create --fields" $CLI record create --table-id "$TBL" --fields '{"Title":"r1","Status":"todo","Num":1}'
|
|
smoke "record create --records" $CLI record create --table-id "$TBL" --records '[{"fields":{"Title":"r2"}},{"fields":{"Title":"r3"}}]'
|
|
REC=$($CLI record list --table-id "$TBL" --json | j "d['records'][0]['id']")
|
|
STATUS_FLD=$($CLI field list "$TBL" --json | j "[f['id'] for f in d if f['name']=='Status'][0]")
|
|
smoke "record list" $CLI record list --table-id "$TBL"
|
|
smoke "record get" $CLI record get --table-id "$TBL"
|
|
smoke "record get --filter" $CLI record get --table-id "$TBL" --filter "{\"conjunction\":\"and\",\"filterSet\":[{\"fieldId\":\"$STATUS_FLD\",\"operator\":\"is\",\"value\":\"todo\"}]}"
|
|
smoke "record get --take/--skip" $CLI record get --table-id "$TBL" --take 2 --skip 1
|
|
smoke "record get --page-size" $CLI record get --table-id "$TBL" --page-size 1 --page 2
|
|
smoke "record get --search" $CLI record get --table-id "$TBL" --search r1
|
|
smoke "record get --projection" $CLI record get --table-id "$TBL" --projection '["Title"]'
|
|
smoke "record get-one" $CLI record get-one "$REC" --table-id "$TBL"
|
|
smoke "record get --record-id" $CLI record get --record-id "$REC" --table-id "$TBL"
|
|
smoke "record update" $CLI record update --table-id "$TBL" --records "[{\"id\":\"$REC\",\"fields\":{\"Num\":42}}]"
|
|
smoke "record visible-choices" $CLI field visible-choices "$TBL" "$REC" Status
|
|
smoke "record delete (positional)" $CLI record delete "$REC" --table-id "$TBL"
|
|
smoke "record delete (--csv)" $CLI record delete --record-ids "$($CLI record list --table-id "$TBL" --json | j "','.join(r['id'] for r in d['records'])")" --table-id "$TBL"
|
|
|
|
# ---------- view ----------
|
|
smoke "view list (positional)" $CLI view list "$TBL"
|
|
smoke "view list --table-id" $CLI view list --table-id "$TBL"
|
|
VIW=$($CLI view create --table-id "$TBL" --name smoke_v --json | j "d['id']")
|
|
smoke "view create" $CLI view get "$VIW" --table-id "$TBL"
|
|
smoke "view get" $CLI view get "$VIW" --table-id "$TBL"
|
|
smoke "view update --name" $CLI view update "$VIW" --table-id "$TBL" --name smoke_v2
|
|
smoke "view update --filter" $CLI view update "$VIW" --table-id "$TBL" --filter "{\"conjunction\":\"and\",\"filterSet\":[{\"fieldId\":\"$STATUS_FLD\",\"operator\":\"is\",\"value\":\"todo\"}]}"
|
|
smoke "view update --sort" $CLI view update "$VIW" --table-id "$TBL" --sort "[{\"fieldId\":\"$STATUS_FLD\",\"order\":\"desc\"}]"
|
|
smoke "view update --group" $CLI view update "$VIW" --table-id "$TBL" --group "[{\"fieldId\":\"$STATUS_FLD\",\"order\":\"asc\"}]"
|
|
|
|
# ---------- preset ----------
|
|
smoke "preset list" $CLI preset list "$TBL"
|
|
PID=$($CLI preset create --table-id "$TBL" --view-id "$VIW" --name smoke_p --json | j "d['id']") || PID=""
|
|
if [ -n "${PID:-}" ]; then
|
|
smoke "preset create" $CLI preset list "$TBL"
|
|
smoke "preset update" $CLI preset update "$TBL" "$PID" --name smoke_p2
|
|
smoke "preset delete" $CLI preset delete "$TBL" "$PID"
|
|
fi
|
|
|
|
# ---------- workflow / trigger / action / runs / fire ----------
|
|
WF=$($CLI workflow create --name smoke_wf --json | j "d['id']")
|
|
smoke "workflow create" $CLI workflow get "$WF"
|
|
smoke "workflow list" $CLI workflow list
|
|
smoke "workflow get" $CLI workflow get "$WF"
|
|
smoke "workflow update" $CLI workflow update "$WF" --name smoke_wf2 --description d
|
|
smoke "trigger webhook" $CLI trigger set "$WF" --type webhook
|
|
smoke "trigger scheduledTime" $CLI trigger set "$WF" --type scheduledTime --schedule '{"timing":{"type":"hours","interval":1}}'
|
|
smoke "trigger recordCreated" $CLI trigger set "$WF" --type recordCreated --table-id "$TBL"
|
|
smoke "trigger recordUpdated" $CLI trigger set "$WF" --type recordUpdated --table-id "$TBL" --field-ids '[]'
|
|
smoke "trigger recordCondition" $CLI trigger set "$WF" --type recordCondition --table-id "$TBL" --conditions "{\"conjunction\":\"and\",\"filterSet\":[{\"fieldId\":\"$STATUS_FLD\",\"operator\":\"is\",\"value\":\"done\"}]}"
|
|
smoke "trigger formSubmitted" $CLI trigger set "$WF" --type formSubmitted --table-id "$TBL"
|
|
$CLI field create --table-id "$TBL" --name Go --type button --options "{\"label\":\"Go\",\"style\":\"blue\",\"workflow\":{\"id\":\"$WF\",\"name\":\"smoke_wf2\"}}" >/dev/null
|
|
BTN=$($CLI field list "$TBL" --json | j "[f['id'] for f in d if f['name']=='Go'][0]")
|
|
smoke "trigger buttonClick" $CLI trigger set "$WF" --type buttonClick --table-id "$TBL" --field-ids "[\"$BTN\"]"
|
|
$CLI record create --table-id "$TBL" --fields '{"Title":"fire-test"}' >/dev/null
|
|
FREC=$($CLI record list --table-id "$TBL" --json | j "[r['id'] for r in d['records'] if r['fields'].get('Title')=='fire-test'][0]")
|
|
$CLI action add "$WF" --type script --code 'return {ok:true}' --json > /tmp/smoke_wf.json
|
|
AID=$(j "[n['id'] for n in d['nodes'] if n['category']=='script'][-1]" < /tmp/smoke_wf.json)
|
|
smoke "action add script" $CLI workflow get "$WF"
|
|
smoke "action add sendNotification" $CLI action add "$WF" --type sendNotification --title smoke --message hello
|
|
smoke "action update" $CLI action update "$WF" "$AID" --code 'return {ok:2}'
|
|
smoke "action delete" $CLI action delete "$WF" "$AID"
|
|
smoke "workflow activate" $CLI workflow activate "$WF"
|
|
smoke "fire button-click" $CLI fire button-click "$TBL" "$FREC" "$BTN"
|
|
sleep 4
|
|
smoke "runs list" $CLI runs list "$WF"
|
|
RUN=$($CLI runs list "$WF" --json | j "d[0]['id']" 2>/dev/null) || RUN=""
|
|
[ -n "${RUN:-}" ] && smoke "runs get" $CLI runs get "$WF" "$RUN"
|
|
[ -n "${RUN:-}" ] && smoke "runs rerun" $CLI runs rerun "$WF" "$RUN"
|
|
smoke "trigger set back to webhook" $CLI trigger set "$WF" --type webhook
|
|
smoke "fire webhook" $CLI fire webhook "$BASE" "$WF" --body '{"x":1}'
|
|
smoke "workflow test" $CLI workflow test "$WF"
|
|
|
|
# ---------- secret (before workflow delete — secrets belong to the workflow) ----------
|
|
SID=$($CLI secret set "$WF" --name SMOKE_KEY --value v1 --description smoke --json | j "d['id']") || SID=""
|
|
smoke "secret set" $CLI secret list "$WF"
|
|
smoke "secret list" $CLI secret list "$WF"
|
|
smoke "secret update" $CLI secret update "$WF" --name SMOKE_KEY --value v2
|
|
[ -n "${SID:-}" ] && smoke "secret delete" $CLI secret delete "$WF" "$SID"
|
|
|
|
smoke "workflow deactivate" $CLI workflow deactivate "$WF"
|
|
smoke "workflow delete" $CLI workflow delete "$WF"
|
|
|
|
# ---------- subscription ----------
|
|
SUB=$($CLI subscription create --name smoke_sub --table-id "$TBL" --send-mode instant --json 2>/dev/null | j "d.get('id','')" 2>/dev/null) || SUB=""
|
|
if [ -n "${SUB:-}" ]; then
|
|
smoke "subscription create" $CLI subscription get "$SUB"
|
|
smoke "subscription get" $CLI subscription get "$SUB"
|
|
smoke "subscription list" $CLI subscription list
|
|
smoke "subscription delete" $CLI subscription delete "$SUB"
|
|
else
|
|
echo "warn subscription create returned no id — check manually"
|
|
fi
|
|
|
|
# ---------- plugin (reads only; register/install need a real bundle) ----------
|
|
smoke "plugin list" $CLI plugin list
|
|
smoke "plugin installs" $CLI plugin installs
|
|
|
|
# ---------- table delete (destructive, scratch only) ----------
|
|
smoke "table delete" $CLI table delete "$TBL"
|
|
|
|
echo
|
|
echo "==== smoke: $PASS passed, $FAIL failed ==="
|
|
[ $FAIL -gt 0 ] && { printf 'failed: %s\n' "${FAILED_CASES[@]}"; exit 1; }
|
|
exit 0
|