test(live): expand live test coverage

This commit is contained in:
Peter Steinberger 2026-01-17 19:15:47 +00:00
parent bf6bf372ab
commit bd91323552
15 changed files with 758 additions and 251 deletions

View File

@ -1173,7 +1173,18 @@ Go test wrapper (opt-in):
GOG_LIVE=1 go test -tags=integration ./internal/integration -run Live
```
Optional env: `GOG_LIVE_FAST=1`, `GOG_LIVE_SKIP=groups,keep`, `GOG_LIVE_AUTH=all,groups`, `GOG_LIVE_ALLOW_NONTEST=1`.
Optional env:
- `GOG_LIVE_FAST=1`
- `GOG_LIVE_SKIP=groups,keep`
- `GOG_LIVE_AUTH=all,groups`
- `GOG_LIVE_ALLOW_NONTEST=1`
- `GOG_LIVE_EMAIL_TEST=steipete+gogtest@gmail.com`
- `GOG_LIVE_GROUP_EMAIL=group@domain`
- `GOG_LIVE_CLASSROOM_COURSE=<courseId>`
- `GOG_LIVE_CLASSROOM_CREATE=1`
- `GOG_LIVE_TRACK=1`
- `GOG_KEEP_SERVICE_ACCOUNT=/path/to/service-account.json`
- `GOG_KEEP_IMPERSONATE=user@workspace-domain`
### Make Shortcut

View File

@ -21,10 +21,20 @@ Options:
--auth <services> Re-auth before running (e.g., all,groups)
-h, --help Show this help
Skip keys:
auth-alias, enable-commands, gmail, drive, docs, sheets, slides,
calendar, calendar-enterprise, tasks, contacts, people,
groups, keep, classroom
Skip keys (base):
auth-alias, enable-commands, gmail, gmail-settings, gmail-delegates, gmail-batch-delete, drive, docs, sheets, slides,
calendar, calendar-enterprise, calendar-respond, calendar-team, calendar-users,
tasks, contacts, people, groups, keep, classroom
Env:
GOG_LIVE_EMAIL_TEST=steipete+gogtest@gmail.com
GOG_LIVE_GROUP_EMAIL=<group@domain>
GOG_LIVE_CLASSROOM_COURSE=<courseId>
GOG_LIVE_CLASSROOM_CREATE=1
GOG_LIVE_TRACK=1
GOG_LIVE_ALLOW_NONTEST=1
GOG_KEEP_SERVICE_ACCOUNT=/path/to/service-account.json
GOG_KEEP_IMPERSONATE=user@workspace-domain
USAGE
}
@ -62,7 +72,14 @@ while [ $# -gt 0 ]; do
;;
esac
shift
done
done
if [ -n "${GOG_LIVE_FAST:-}" ]; then
FAST=true
fi
if [ -z "$AUTH_SERVICES" ] && [ -n "${GOG_LIVE_AUTH:-}" ]; then
AUTH_SERVICES="$GOG_LIVE_AUTH"
fi
SKIP="${SKIP:-${GOG_LIVE_SKIP:-}}"
if [ "$FAST" = true ]; then
@ -97,216 +114,32 @@ fi
echo "Using account: $ACCOUNT"
is_test_account() {
local a
a=$(echo "$1" | tr 'A-Z' 'a-z')
case "$a" in
*test*|*bot*|*sandbox*|*qa*|*staging*|*dev*|*@example.com)
return 0
;;
esac
case "$a" in
*+*)
return 0
;;
esac
return 1
}
EMAIL_TEST="${GOG_LIVE_EMAIL_TEST:-steipete+gogtest@gmail.com}"
TS=$(date +%Y%m%d%H%M%S)
LIVE_TMP=$(mktemp -d "${TMPDIR:-/tmp}/gog-live-$TS-XXXX")
trap 'rm -rf "$LIVE_TMP"' EXIT
if [ "$ALLOW_NONTEST" = false ] && [ -z "${GOG_LIVE_ALLOW_NONTEST:-}" ]; then
if ! is_test_account "$ACCOUNT"; then
echo "Refusing to run live tests against non-test account: $ACCOUNT" >&2
echo "Pass --allow-nontest or set GOG_LIVE_ALLOW_NONTEST=1 to override." >&2
exit 2
fi
fi
source scripts/live-tests/common.sh
source scripts/live-tests/core.sh
source scripts/live-tests/gmail.sh
source scripts/live-tests/drive.sh
source scripts/live-tests/docs.sh
source scripts/live-tests/sheets.sh
source scripts/live-tests/slides.sh
source scripts/live-tests/calendar.sh
source scripts/live-tests/tasks.sh
source scripts/live-tests/contacts.sh
source scripts/live-tests/people.sh
source scripts/live-tests/workspace.sh
source scripts/live-tests/classroom.sh
ensure_test_account
if [ -n "$AUTH_SERVICES" ]; then
$BIN auth add "$ACCOUNT" --services "$AUTH_SERVICES"
fi
TS=$(date +%Y%m%d%H%M%S)
ACCOUNT_ARGS=(--account "$ACCOUNT")
gog() {
"$BIN" "${ACCOUNT_ARGS[@]}" "$@"
}
skip() {
local key="$1"
[ -n "$SKIP" ] || return 1
IFS=',' read -r -a items <<<"$SKIP"
for item in "${items[@]}"; do
if [ "$item" = "$key" ]; then
return 0
fi
done
return 1
}
extract_id() {
$PY -c 'import json,sys
obj=json.load(sys.stdin)
def find_id(x):
if isinstance(x, dict):
for key in ("id", "draftId", "spreadsheetId", "presentationId", "documentId"):
if isinstance(x.get(key), str):
return x[key]
for v in x.values():
r=find_id(v)
if r:
return r
if isinstance(x, list):
for v in x:
r=find_id(v)
if r:
return r
return ""
print(find_id(obj))' <<<"$1"
}
extract_field() {
local value="$1"
local field="$2"
$PY -c 'import json,sys
obj=json.load(sys.stdin)
key=sys.argv[1]
def find_field(x, k):
if isinstance(x, dict):
if k in x and isinstance(x[k], str):
return x[k]
for v in x.values():
r=find_field(v, k)
if r:
return r
if isinstance(x, list):
for v in x:
r=find_field(v, k)
if r:
return r
return ""
print(find_field(obj, key))' "$field" <<<"$value"
}
extract_tasklist_id() {
$PY -c 'import json,sys
obj=json.load(sys.stdin)
for key in ("tasklists","lists","items"):
if isinstance(obj, dict) and obj.get(key):
print(obj[key][0].get("id",""))
sys.exit(0)
print("")' <<<"$1"
}
extract_task_ids() {
$PY -c 'import json,sys
obj=json.load(sys.stdin)
ids=[]
if isinstance(obj, dict) and "tasks" in obj:
ids=[t.get("id") for t in obj.get("tasks",[]) if t.get("id")]
elif isinstance(obj, dict) and "task" in obj:
if obj["task"].get("id"):
ids=[obj["task"]["id"]]
print("\n".join(ids))' <<<"$1"
}
run_required() {
local key="$1"
local label="$2"
shift 2
if skip "$key"; then
echo "==> $label (skipped)"
return 0
fi
echo "==> $label"
"$@"
}
run_optional() {
local key="$1"
local label="$2"
shift 2
if skip "$key"; then
echo "==> $label (skipped)"
return 0
fi
echo "==> $label (optional)"
if "$@"; then
echo "ok"
return 0
fi
echo "skipped/failed"
if [ "$STRICT" = true ]; then
return 1
fi
return 0
}
run_required "time" "time now" "$BIN" time now --json >/dev/null
if ! skip "auth-alias"; then
alias_name="smoke-$TS"
run_required "auth-alias" "auth alias set" "$BIN" auth alias set "$alias_name" "$ACCOUNT" --json >/dev/null
run_required "auth-alias" "auth alias list" "$BIN" auth alias list --json >/dev/null
run_required "auth-alias" "auth alias unset" "$BIN" auth alias unset "$alias_name" --json >/dev/null
fi
if ! skip "enable-commands"; then
run_required "enable-commands" "enable-commands allow time" "$BIN" --enable-commands time time now --json >/dev/null
if $BIN --enable-commands time gmail labels list >/dev/null 2>&1; then
echo "Expected enable-commands to block gmail, but it succeeded" >&2
exit 1
else
echo "enable-commands block OK"
fi
fi
if ! skip "gmail"; then
run_required "gmail" "gmail labels list" gog gmail labels list --json >/dev/null
DRAFT_JSON=$(gog gmail drafts create --to "$ACCOUNT" --subject "gogcli smoke $TS" --body "smoke" --json)
DRAFT_ID=$(extract_field "$DRAFT_JSON" draftId)
[ -n "$DRAFT_ID" ] || { echo "Failed to parse draft id" >&2; exit 1; }
run_required "gmail" "gmail drafts get" gog gmail drafts get "$DRAFT_ID" --json >/dev/null
run_required "gmail" "gmail drafts delete" gog gmail drafts delete "$DRAFT_ID" --force >/dev/null
fi
if ! skip "drive"; then
run_required "drive" "drive ls" gog drive ls --json --max 1 >/dev/null
FOLDER_JSON=$(gog drive mkdir "gogcli-smoke-$TS" --json)
FOLDER_ID=$(extract_id "$FOLDER_JSON")
[ -n "$FOLDER_ID" ] || { echo "Failed to parse drive folder id" >&2; exit 1; }
run_required "drive" "drive get folder" gog drive get "$FOLDER_ID" --json >/dev/null
run_required "drive" "drive delete folder" gog drive delete "$FOLDER_ID" --force >/dev/null
fi
if ! skip "docs"; then
DOC_JSON=$(gog docs create "gogcli-smoke-$TS" --json)
DOC_ID=$(extract_id "$DOC_JSON")
[ -n "$DOC_ID" ] || { echo "Failed to parse doc id" >&2; exit 1; }
run_required "docs" "drive get doc" gog drive get "$DOC_ID" --json >/dev/null
run_required "docs" "drive delete doc" gog drive delete "$DOC_ID" --force >/dev/null
fi
if ! skip "sheets"; then
SHEET_JSON=$(gog sheets create "gogcli-smoke-$TS" --json)
SHEET_ID=$(extract_id "$SHEET_JSON")
[ -n "$SHEET_ID" ] || { echo "Failed to parse sheet id" >&2; exit 1; }
run_required "sheets" "drive get sheet" gog drive get "$SHEET_ID" --json >/dev/null
run_required "sheets" "drive delete sheet" gog drive delete "$SHEET_ID" --force >/dev/null
fi
if ! skip "slides"; then
SLIDES_JSON=$(gog slides create "gogcli-smoke-$TS" --json)
SLIDES_ID=$(extract_id "$SLIDES_JSON")
[ -n "$SLIDES_ID" ] || { echo "Failed to parse slides id" >&2; exit 1; }
run_required "slides" "drive get slides" gog drive get "$SLIDES_ID" --json >/dev/null
run_required "slides" "drive delete slides" gog drive delete "$SLIDES_ID" --force >/dev/null
fi
if ! skip "calendar"; then
read -r START END DAY1 DAY2 <<<"$($PY - <<'PY'
read -r START END DAY1 DAY2 <<<"$($PY - <<'PY'
import datetime
now=datetime.datetime.now(datetime.timezone.utc).replace(minute=0, second=0, microsecond=0)
start=now + datetime.timedelta(hours=1)
@ -315,46 +148,17 @@ print(start.strftime('%Y-%m-%dT%H:%M:%SZ'), end.strftime('%Y-%m-%dT%H:%M:%SZ'),
PY
)"
EV_JSON=$(gog calendar create primary --summary "gogcli-smoke-$TS" --from "$START" --to "$END" --json)
EV_ID=$(extract_id "$EV_JSON")
[ -n "$EV_ID" ] || { echo "Failed to parse calendar event id" >&2; exit 1; }
run_required "calendar" "calendar event get" gog calendar event primary "$EV_ID" --json >/dev/null
run_required "calendar" "calendar propose-time" gog calendar propose-time primary "$EV_ID" --json >/dev/null
run_required "calendar" "calendar delete event" gog calendar delete primary "$EV_ID" --force >/dev/null
if ! skip "calendar-enterprise"; then
run_optional "calendar-enterprise" "calendar focus-time" gog calendar create primary --event-type focus-time --from "$START" --to "$END" --json >/dev/null 2>&1 || true
run_optional "calendar-enterprise" "calendar out-of-office" gog calendar create primary --event-type out-of-office --from "$DAY1" --to "$DAY2" --all-day --json >/dev/null 2>&1 || true
run_optional "calendar-enterprise" "calendar working-location" gog calendar create primary --event-type working-location --working-location-type office --working-office-label "HQ" --from "$DAY1" --to "$DAY2" --json >/dev/null 2>&1 || true
fi
fi
if ! skip "tasks"; then
LIST_JSON=$(gog tasks lists --json --max 1)
LIST_ID=$(extract_tasklist_id "$LIST_JSON")
[ -n "$LIST_ID" ] || { echo "No task list found" >&2; exit 1; }
TASK_JSON=$(gog tasks add "$LIST_ID" --title "gogcli-smoke-$TS" --due "$DAY1" --repeat daily --repeat-count 2 --json)
TASK_IDS=$(extract_task_ids "$TASK_JSON")
[ -n "$TASK_IDS" ] || { echo "Failed to parse task ids" >&2; exit 1; }
FIRST_TASK_ID=$(echo "$TASK_IDS" | head -n1)
run_required "tasks" "tasks get" gog tasks get "$LIST_ID" "$FIRST_TASK_ID" --json >/dev/null
while IFS= read -r tid; do
[ -n "$tid" ] && run_required "tasks" "tasks delete" gog tasks delete "$LIST_ID" "$tid" --force >/dev/null
done <<<"$TASK_IDS"
fi
if ! skip "contacts"; then
run_required "contacts" "contacts list" gog contacts list --json --max 1 >/dev/null
CONTACT_JSON=$(gog contacts create --given "gogcli" --family "smoke-$TS" --email "gogcli-smoke-$TS@example.com" --json)
CONTACT_ID=$(extract_field "$CONTACT_JSON" resourceName)
[ -n "$CONTACT_ID" ] || { echo "Failed to parse contact resourceName" >&2; exit 1; }
run_required "contacts" "contacts delete" gog contacts delete "$CONTACT_ID" --force >/dev/null
fi
run_required "people" "people me" gog people me --json >/dev/null
run_optional "groups" "groups list" gog groups list --json --max 1 >/dev/null 2>&1
run_optional "keep" "keep list" gog keep list --json >/dev/null 2>&1
run_optional "classroom" "classroom list" gog classroom courses list --json --max 1 >/dev/null 2>&1
run_core_tests
run_gmail_tests
run_drive_tests
run_docs_tests
run_sheets_tests
run_slides_tests
run_calendar_tests
run_tasks_tests
run_contacts_tests
run_people_tests
run_workspace_tests
run_classroom_tests
echo "Live tests complete."

View File

@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail
run_calendar_tests() {
if skip "calendar"; then
echo "==> calendar (skipped)"
return 0
fi
read -r START END DAY1 DAY2 <<<"$($PY - <<'PY'
import datetime
now=datetime.datetime.now(datetime.timezone.utc).replace(minute=0, second=0, microsecond=0)
start=now + datetime.timedelta(hours=1)
end=start + datetime.timedelta(hours=1)
print(start.strftime('%Y-%m-%dT%H:%M:%SZ'), end.strftime('%Y-%m-%dT%H:%M:%SZ'), start.strftime('%Y-%m-%d'), (start+datetime.timedelta(days=1)).strftime('%Y-%m-%d'))
PY
)"
run_required "calendar" "calendar list" gog calendar calendars --json --max 1 >/dev/null
run_required "calendar" "calendar acl" gog calendar acl primary --json --max 1 >/dev/null
run_required "calendar" "calendar colors" gog calendar colors --json >/dev/null
run_required "calendar" "calendar time" gog calendar time --json >/dev/null
local ev_json ev_id
ev_json=$(gog calendar create primary --summary "gogcli-smoke-$TS" --from "$START" --to "$END" --location "Test" --send-updates none --json)
ev_id=$(extract_id "$ev_json")
[ -n "$ev_id" ] || { echo "Failed to parse calendar event id" >&2; exit 1; }
run_required "calendar" "calendar event get" gog calendar event primary "$ev_id" --json >/dev/null
run_required "calendar" "calendar update" gog calendar update primary "$ev_id" --summary "gogcli-smoke-updated-$TS" --json >/dev/null
run_required "calendar" "calendar events list" gog calendar events primary --from "$START" --to "$END" --json --max 5 >/dev/null
run_required "calendar" "calendar search" gog calendar search "gogcli-smoke" --from "$START" --to "$END" --json --max 5 >/dev/null
run_required "calendar" "calendar freebusy" gog calendar freebusy primary --from "$START" --to "$END" --json >/dev/null
run_required "calendar" "calendar conflicts" gog calendar conflicts --from "$START" --to "$END" --json >/dev/null
run_optional "calendar-respond" "calendar respond" gog calendar respond primary "$ev_id" --status accepted --json >/dev/null
run_required "calendar" "calendar delete event" gog calendar delete primary "$ev_id" --force >/dev/null
if ! skip "calendar-enterprise"; then
run_optional "calendar-enterprise" "calendar focus-time" gog calendar create primary --event-type focus-time --from "$START" --to "$END" --json >/dev/null 2>&1 || true
run_optional "calendar-enterprise" "calendar out-of-office" gog calendar create primary --event-type out-of-office --from "$DAY1" --to "$DAY2" --all-day --json >/dev/null 2>&1 || true
run_optional "calendar-enterprise" "calendar working-location" gog calendar create primary --event-type working-location --working-location-type office --working-office-label "HQ" --from "$DAY1" --to "$DAY2" --json >/dev/null 2>&1 || true
fi
if [ -n "${GOG_LIVE_GROUP_EMAIL:-}" ]; then
run_optional "calendar-team" "calendar team" gog calendar team "$GOG_LIVE_GROUP_EMAIL" --json --max 5 >/dev/null
fi
run_optional "calendar-users" "calendar users list" gog calendar users --json --max 1 >/dev/null
}

View File

@ -0,0 +1,87 @@
#!/usr/bin/env bash
set -euo pipefail
run_classroom_tests() {
if skip "classroom"; then
echo "==> classroom (skipped)"
return 0
fi
run_optional "classroom" "classroom profile get" gog classroom profile get --json >/dev/null
run_optional "classroom" "classroom courses list" gog classroom courses list --json --max 1 >/dev/null
if [ -n "${GOG_LIVE_CLASSROOM_COURSE:-}" ]; then
local course_id cw_json cw_id
course_id="$GOG_LIVE_CLASSROOM_COURSE"
run_optional "classroom" "classroom courses get" gog classroom courses get "$course_id" --json >/dev/null
run_optional "classroom" "classroom courses url" gog classroom courses url "$course_id" --json >/dev/null
run_optional "classroom" "classroom roster" gog classroom roster "$course_id" --students --teachers --max 1 --json >/dev/null
run_optional "classroom" "classroom students list" gog classroom students "$course_id" --max 1 --json >/dev/null
run_optional "classroom" "classroom teachers list" gog classroom teachers "$course_id" --max 1 --json >/dev/null
run_optional "classroom" "classroom coursework list" gog classroom coursework "$course_id" --max 1 --json >/dev/null
run_optional "classroom" "classroom materials list" gog classroom materials "$course_id" --max 1 --json >/dev/null
run_optional "classroom" "classroom announcements list" gog classroom announcements "$course_id" --max 1 --json >/dev/null
run_optional "classroom" "classroom topics list" gog classroom topics "$course_id" --max 1 --json >/dev/null
cw_json=$(gog classroom coursework "$course_id" --max 1 --json 2>/dev/null || true)
cw_id=$(extract_id "$cw_json")
if [ -n "$cw_id" ]; then
run_optional "classroom" "classroom submissions list" gog classroom submissions "$course_id" "$cw_id" --max 1 --json >/dev/null
fi
else
if [ "${STRICT:-false}" = true ]; then
echo "Missing GOG_LIVE_CLASSROOM_COURSE for classroom coverage." >&2
return 1
fi
echo "==> classroom (optional; set GOG_LIVE_CLASSROOM_COURSE to expand)"
fi
if [ -n "${GOG_LIVE_CLASSROOM_CREATE:-}" ]; then
local course_json course_id topic_json topic_id announcement_json announcement_id material_json material_id coursework_json coursework_id
echo "==> classroom courses create"
course_json=$(gog classroom courses create --name "gogcli-smoke-$TS" --section "gogcli" --state ACTIVE --json)
course_id=$(extract_id "$course_json")
[ -n "$course_id" ] || { echo "Failed to parse course id" >&2; exit 1; }
run_required "classroom" "classroom courses update" gog classroom courses update "$course_id" --name "gogcli-smoke-updated-$TS" --json >/dev/null
run_required "classroom" "classroom courses archive" gog classroom courses archive "$course_id" --json >/dev/null
run_required "classroom" "classroom courses unarchive" gog classroom courses unarchive "$course_id" --json >/dev/null
echo "==> classroom topics create"
topic_json=$(gog classroom topics create "$course_id" --name "gogcli topic $TS" --json)
topic_id=$(extract_id "$topic_json")
echo "==> classroom announcements create"
announcement_json=$(gog classroom announcements create "$course_id" --text "gogcli announcement $TS" --state DRAFT --json)
announcement_id=$(extract_id "$announcement_json")
echo "==> classroom materials create"
material_json=$(gog classroom materials create "$course_id" --title "gogcli material $TS" --state DRAFT --json)
material_id=$(extract_id "$material_json")
echo "==> classroom coursework create"
coursework_json=$(gog classroom coursework create "$course_id" --title "gogcli coursework $TS" --type ASSIGNMENT --state DRAFT --max-points 10 --json)
coursework_id=$(extract_id "$coursework_json")
if [ -n "$announcement_id" ]; then
run_required "classroom" "classroom announcements update" gog classroom announcements update "$course_id" "$announcement_id" --text "gogcli announcement updated $TS" --json >/dev/null
run_required "classroom" "classroom announcements delete" gog --force classroom announcements delete "$course_id" "$announcement_id" --json >/dev/null
fi
if [ -n "$material_id" ]; then
run_required "classroom" "classroom materials update" gog classroom materials update "$course_id" "$material_id" --title "gogcli material updated $TS" --json >/dev/null
run_required "classroom" "classroom materials delete" gog --force classroom materials delete "$course_id" "$material_id" --json >/dev/null
fi
if [ -n "$coursework_id" ]; then
run_required "classroom" "classroom coursework update" gog classroom coursework update "$course_id" "$coursework_id" --title "gogcli coursework updated $TS" --state DRAFT --json >/dev/null
run_required "classroom" "classroom coursework delete" gog --force classroom coursework delete "$course_id" "$coursework_id" --json >/dev/null
fi
if [ -n "$topic_id" ]; then
run_required "classroom" "classroom topics update" gog classroom topics update "$course_id" "$topic_id" --name "gogcli topic updated $TS" --json >/dev/null
run_required "classroom" "classroom topics delete" gog --force classroom topics delete "$course_id" "$topic_id" --json >/dev/null
fi
run_required "classroom" "classroom courses delete" gog --force classroom courses delete "$course_id" --json >/dev/null
fi
}

View File

@ -0,0 +1,192 @@
#!/usr/bin/env bash
set -euo pipefail
PY="${PYTHON:-python3}"
if ! command -v "$PY" >/dev/null 2>&1; then
PY="python"
fi
skip() {
local key="$1"
[ -n "${SKIP:-}" ] || return 1
IFS=',' read -r -a items <<<"$SKIP"
for item in "${items[@]}"; do
if [ "$item" = "$key" ]; then
return 0
fi
done
return 1
}
run_required() {
local key="$1"
local label="$2"
shift 2
if skip "$key"; then
echo "==> $label (skipped)"
return 0
fi
echo "==> $label"
"$@"
}
run_optional() {
local key="$1"
local label="$2"
shift 2
if skip "$key"; then
echo "==> $label (skipped)"
return 0
fi
echo "==> $label (optional)"
if "$@"; then
echo "ok"
return 0
fi
echo "skipped/failed"
if [ "${STRICT:-false}" = true ]; then
return 1
fi
return 0
}
extract_id() {
$PY -c 'import json,sys
obj=json.load(sys.stdin)
def find_id(x):
if isinstance(x, dict):
for key in ("id", "draftId", "spreadsheetId", "presentationId", "documentId", "topicId"):
if isinstance(x.get(key), str):
return x[key]
for v in x.values():
r=find_id(v)
if r:
return r
if isinstance(x, list):
for v in x:
r=find_id(v)
if r:
return r
return ""
print(find_id(obj))' <<<"$1"
}
extract_field() {
local value="$1"
local field="$2"
$PY -c 'import json,sys
obj=json.load(sys.stdin)
key=sys.argv[1]
def find_field(x, k):
if isinstance(x, dict):
if k in x and isinstance(x[k], str):
return x[k]
for v in x.values():
r=find_field(v, k)
if r:
return r
if isinstance(x, list):
for v in x:
r=find_field(v, k)
if r:
return r
return ""
print(find_field(obj, key))' "$field" <<<"$value"
}
extract_tasklist_id() {
$PY -c 'import json,sys
obj=json.load(sys.stdin)
for key in ("tasklists","lists","items"):
if isinstance(obj, dict) and obj.get(key):
print(obj[key][0].get("id",""))
sys.exit(0)
print("")' <<<"$1"
}
extract_task_ids() {
$PY -c 'import json,sys
obj=json.load(sys.stdin)
ids=[]
if isinstance(obj, dict) and "tasks" in obj:
ids=[t.get("id") for t in obj.get("tasks",[]) if t.get("id")]
elif isinstance(obj, dict) and "task" in obj:
if obj["task"].get("id"):
ids=[obj["task"]["id"]]
print("\n".join(ids))' <<<"$1"
}
extract_permission_id() {
local value="$1"
local email="$2"
$PY -c 'import json,sys
obj=json.load(sys.stdin)
email=sys.argv[1].lower()
base=email
if "@" in email:
local, domain = email.split("@", 1)
if "+" in local:
base = local.split("+", 1)[0] + "@" + domain
emails={email, base}
def find_permissions(x):
if isinstance(x, dict):
if isinstance(x.get("permissions"), list):
return x["permissions"]
for v in x.values():
r = find_permissions(v)
if r is not None:
return r
if isinstance(x, list):
for v in x:
r = find_permissions(v)
if r is not None:
return r
return None
perms = find_permissions(obj) or []
for p in perms:
if not isinstance(p, dict):
continue
addr = (p.get("emailAddress") or "").lower()
if addr in emails:
pid = p.get("id") or ""
if pid:
print(pid)
sys.exit(0)
print("")' "$email" <<<"$value"
}
gog() {
"$BIN" --account "$ACCOUNT" "$@"
}
is_test_account() {
local a
a=$(echo "$1" | tr 'A-Z' 'a-z')
case "$a" in
*test*|*bot*|*sandbox*|*qa*|*staging*|*dev*|*@example.com)
return 0
;;
esac
case "$a" in
*+*)
return 0
;;
esac
return 1
}
ensure_test_account() {
if [ "${ALLOW_NONTEST:-false}" = true ] || [ -n "${GOG_LIVE_ALLOW_NONTEST:-}" ]; then
return 0
fi
if ! is_test_account "$ACCOUNT"; then
echo "Refusing to run live tests against non-test account: $ACCOUNT" >&2
echo "Pass --allow-nontest or set GOG_LIVE_ALLOW_NONTEST=1 to override." >&2
exit 2
fi
}

View File

@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
run_contacts_tests() {
if skip "contacts"; then
echo "==> contacts (skipped)"
return 0
fi
run_required "contacts" "contacts list" gog contacts list --json --max 1 >/dev/null
local contact_json contact_id
contact_json=$(gog contacts create --given "gogcli" --family "smoke-$TS" --email "gogcli-smoke-$TS@example.com" --phone "+1555555$TS" --json)
contact_id=$(extract_field "$contact_json" resourceName)
[ -n "$contact_id" ] || { echo "Failed to parse contact resourceName" >&2; exit 1; }
run_required "contacts" "contacts get" gog contacts get "$contact_id" --json >/dev/null
run_required "contacts" "contacts update" gog contacts update "$contact_id" --given "gogcli" --family "smoke-updated-$TS" --email "gogcli-smoke-$TS@example.com" --json >/dev/null
run_required "contacts" "contacts search" gog contacts search "gogcli-smoke-$TS@example.com" --json --max 1 >/dev/null
run_required "contacts" "contacts delete" gog contacts delete "$contact_id" --force >/dev/null
run_optional "contacts-directory" "contacts directory list" gog contacts directory list --json --max 1 >/dev/null
run_optional "contacts-directory" "contacts directory search" gog contacts directory search "gogcli" --json --max 1 >/dev/null
run_optional "contacts-other" "contacts other list" gog contacts other list --json --max 1 >/dev/null
run_optional "contacts-other" "contacts other search" gog contacts other search "gogcli" --json --max 1 >/dev/null
}

View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
run_core_tests() {
run_required "time" "time now" "$BIN" time now --json >/dev/null
if ! skip "auth-alias"; then
local alias_name
alias_name="smoke-$TS"
run_required "auth-alias" "auth alias set" "$BIN" auth alias set "$alias_name" "$ACCOUNT" --json >/dev/null
run_required "auth-alias" "auth alias list" "$BIN" auth alias list --json >/dev/null
run_required "auth-alias" "auth alias unset" "$BIN" auth alias unset "$alias_name" --json >/dev/null
fi
if ! skip "enable-commands"; then
run_required "enable-commands" "enable-commands allow time" "$BIN" --enable-commands time time now --json >/dev/null
if $BIN --enable-commands time gmail labels list >/dev/null 2>&1; then
echo "Expected enable-commands to block gmail, but it succeeded" >&2
exit 1
else
echo "enable-commands block OK"
fi
fi
}

View File

@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
run_docs_tests() {
if skip "docs"; then
echo "==> docs (skipped)"
return 0
fi
local doc_json doc_id copy_json copy_id export_path
doc_json=$(gog docs create "gogcli-smoke-doc-$TS" --json)
doc_id=$(extract_id "$doc_json")
[ -n "$doc_id" ] || { echo "Failed to parse doc id" >&2; exit 1; }
run_required "docs" "docs info" gog docs info "$doc_id" --json >/dev/null
run_required "docs" "docs cat" gog docs cat "$doc_id" >/dev/null
export_path="$LIVE_TMP/docs-export-$TS.pdf"
run_required "docs" "docs export" gog docs export "$doc_id" --format pdf --out "$export_path" >/dev/null
copy_json=$(gog docs copy "$doc_id" "gogcli-smoke-doc-copy-$TS" --json)
copy_id=$(extract_id "$copy_json")
[ -n "$copy_id" ] || { echo "Failed to parse doc copy id" >&2; exit 1; }
run_required "docs" "drive delete doc copy" gog drive delete "$copy_id" --force >/dev/null
run_required "docs" "drive delete doc" gog drive delete "$doc_id" --force >/dev/null
}

View File

@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -euo pipefail
run_drive_tests() {
if skip "drive"; then
echo "==> drive (skipped)"
return 0
fi
run_required "drive" "drive ls" gog drive ls --json --max 1 >/dev/null
run_optional "drive" "drive drives list" gog drive drives --json --max 1 >/dev/null
local folder_a_json folder_b_json folder_a_id folder_b_id
folder_a_json=$(gog drive mkdir "gogcli-smoke-a-$TS" --json)
folder_a_id=$(extract_id "$folder_a_json")
[ -n "$folder_a_id" ] || { echo "Failed to parse folder A id" >&2; exit 1; }
folder_b_json=$(gog drive mkdir "gogcli-smoke-b-$TS" --json)
folder_b_id=$(extract_id "$folder_b_json")
[ -n "$folder_b_id" ] || { echo "Failed to parse folder B id" >&2; exit 1; }
local upload_path upload_json file_id
upload_path="$LIVE_TMP/drive-upload-$TS.txt"
printf "drive upload %s\n" "$TS" >"$upload_path"
upload_json=$(gog drive upload "$upload_path" --parent "$folder_a_id" --name "gogcli-smoke-$TS.txt" --json)
file_id=$(extract_id "$upload_json")
[ -n "$file_id" ] || { echo "Failed to parse uploaded file id" >&2; exit 1; }
run_required "drive" "drive get file" gog drive get "$file_id" --json >/dev/null
run_required "drive" "drive rename" gog drive rename "$file_id" "gogcli-smoke-renamed-$TS.txt" >/dev/null
local copy_json copy_id
copy_json=$(gog drive copy "$file_id" "gogcli-smoke-copy-$TS.txt" --json)
copy_id=$(extract_id "$copy_json")
[ -n "$copy_id" ] || { echo "Failed to parse copy id" >&2; exit 1; }
run_required "drive" "drive move" gog drive move "$file_id" --parent "$folder_b_id" --json >/dev/null
run_required "drive" "drive search" gog drive search "name contains 'gogcli-smoke'" --json --max 1 >/dev/null
run_required "drive" "drive permissions" gog drive permissions "$file_id" --json >/dev/null
local share_json perm_id perms_json
share_json=$(gog drive share "$file_id" --email "$EMAIL_TEST" --role reader --json)
perms_json=$(gog drive permissions "$file_id" --json --max 50)
perm_id=$(extract_permission_id "$perms_json" "$EMAIL_TEST")
if [ -z "$perm_id" ]; then
perm_id=$(extract_field "$share_json" permissionId)
fi
[ -n "$perm_id" ] || { echo "Failed to parse permission id" >&2; exit 1; }
run_required "drive" "drive unshare" gog drive unshare "$file_id" "$perm_id" --force >/dev/null
run_required "drive" "drive url" gog drive url "$file_id" --json >/dev/null
local comment_json comment_id
comment_json=$(gog drive comments create "$file_id" "gogcli comment $TS" --json)
comment_id=$(extract_id "$comment_json")
[ -n "$comment_id" ] || { echo "Failed to parse comment id" >&2; exit 1; }
run_required "drive" "drive comments get" gog drive comments get "$file_id" "$comment_id" --json >/dev/null
run_required "drive" "drive comments list" gog drive comments list "$file_id" --json >/dev/null
run_required "drive" "drive comments update" gog drive comments update "$file_id" "$comment_id" "gogcli comment updated $TS" --json >/dev/null
run_required "drive" "drive comments reply" gog drive comments reply "$file_id" "$comment_id" "gogcli reply $TS" --json >/dev/null
run_required "drive" "drive comments delete" gog drive comments delete "$file_id" "$comment_id" --force >/dev/null
local download_path
download_path="$LIVE_TMP/drive-download-$TS.txt"
run_required "drive" "drive download" gog drive download "$file_id" --out "$download_path" >/dev/null
run_required "drive" "drive delete copy" gog drive delete "$copy_id" --force >/dev/null
run_required "drive" "drive delete file" gog drive delete "$file_id" --force >/dev/null
run_required "drive" "drive delete folder A" gog drive delete "$folder_a_id" --force >/dev/null
run_required "drive" "drive delete folder B" gog drive delete "$folder_b_id" --force >/dev/null
}

View File

@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail
run_gmail_tests() {
if skip "gmail"; then
echo "==> gmail (skipped)"
return 0
fi
run_required "gmail" "gmail labels list" gog gmail labels list --json >/dev/null
run_required "gmail" "gmail labels get" gog gmail labels get INBOX --json >/dev/null
if ! skip "gmail-settings"; then
run_required "gmail" "gmail settings sendas list" gog gmail settings sendas list --json >/dev/null
run_required "gmail" "gmail settings vacation get" gog gmail settings vacation get --json >/dev/null
run_required "gmail" "gmail settings filters list" gog gmail settings filters list --json >/dev/null
run_optional "gmail-delegates" "gmail settings delegates list" gog gmail settings delegates list --json >/dev/null
run_required "gmail" "gmail settings forwarding list" gog gmail settings forwarding list --json >/dev/null
run_required "gmail" "gmail settings autoforward get" gog gmail settings autoforward get --json >/dev/null
fi
local draft_json draft_id sent_draft_json sent_draft_msg_id
draft_json=$(gog gmail drafts create --to "$EMAIL_TEST" --subject "gogcli smoke draft $TS" --body "smoke draft" --json)
draft_id=$(extract_field "$draft_json" draftId)
[ -n "$draft_id" ] || { echo "Failed to parse draft id" >&2; exit 1; }
run_required "gmail" "gmail drafts get" gog gmail drafts get "$draft_id" --json >/dev/null
run_required "gmail" "gmail drafts update" gog gmail drafts update "$draft_id" --subject "gogcli smoke draft updated $TS" --body "updated" --json >/dev/null
sent_draft_json=$(gog gmail drafts send "$draft_id" --json)
sent_draft_msg_id=$(extract_field "$sent_draft_json" messageId)
[ -n "$sent_draft_msg_id" ] || { echo "Failed to parse sent draft message id" >&2; exit 1; }
local body_file send_json send_msg_id send_thread_id
body_file="$LIVE_TMP/gmail-body-$TS.txt"
printf "hello from gogcli %s\n" "$TS" >"$body_file"
send_json=$(gog gmail send --to "$EMAIL_TEST" --subject "gogcli smoke send $TS" --body-file "$body_file" --json)
send_msg_id=$(extract_field "$send_json" messageId)
send_thread_id=$(extract_field "$send_json" threadId)
[ -n "$send_msg_id" ] || { echo "Failed to parse send message id" >&2; exit 1; }
run_required "gmail" "gmail get message" gog gmail get "$send_msg_id" --format metadata --json >/dev/null
if [ -n "$send_thread_id" ]; then
run_required "gmail" "gmail thread get" gog gmail thread get "$send_thread_id" --json >/dev/null
run_required "gmail" "gmail thread modify add label" gog gmail thread modify "$send_thread_id" --add STARRED --json >/dev/null
run_required "gmail" "gmail thread modify remove label" gog gmail thread modify "$send_thread_id" --remove STARRED --json >/dev/null
fi
run_required "gmail" "gmail search" gog gmail search "subject:gogcli smoke send $TS" --json >/dev/null
run_required "gmail" "gmail batch modify add" gog gmail batch modify "$send_msg_id" --add STARRED --json >/dev/null
run_required "gmail" "gmail batch modify remove" gog gmail batch modify "$send_msg_id" --remove STARRED --json >/dev/null
if skip "gmail-batch-delete"; then
echo "==> gmail batch delete (skipped)"
else
echo "==> gmail batch delete"
if gog gmail batch delete "$send_msg_id" "$sent_draft_msg_id" --json >/dev/null; then
:
else
echo "gmail batch delete failed; falling back to trash" >&2
gog gmail batch modify "$send_msg_id" "$sent_draft_msg_id" --add TRASH --json >/dev/null || true
if [ "${STRICT:-false}" = true ]; then
return 1
fi
fi
fi
if [ -n "${GOG_LIVE_TRACK:-}" ]; then
run_optional "gmail-track" "gmail send --track" gog gmail send --to "$EMAIL_TEST" --subject "gogcli smoke track $TS" --body-html "<p>track $TS</p>" --track --json >/dev/null
fi
}

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
run_people_tests() {
run_required "people" "people me" gog people me --json >/dev/null
}

View File

@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
run_sheets_tests() {
if skip "sheets"; then
echo "==> sheets (skipped)"
return 0
fi
local sheet_json sheet_id copy_json copy_id export_path
sheet_json=$(gog sheets create "gogcli-smoke-sheet-$TS" --json)
sheet_id=$(extract_id "$sheet_json")
[ -n "$sheet_id" ] || { echo "Failed to parse sheet id" >&2; exit 1; }
run_required "sheets" "sheets metadata" gog sheets metadata "$sheet_id" --json >/dev/null
run_required "sheets" "sheets update" gog sheets update "$sheet_id" "Sheet1!A1:B2" --values-json '[["A1","B1"],["A2","B2"]]' --json >/dev/null
run_required "sheets" "sheets get" gog sheets get "$sheet_id" "Sheet1!A1:B2" --json >/dev/null
run_required "sheets" "sheets append" gog sheets append "$sheet_id" "Sheet1!A3:B3" --values-json '[["A3","B3"]]' --json >/dev/null
run_required "sheets" "sheets format" gog sheets format "$sheet_id" "Sheet1!A1:B1" --format-json '{"textFormat":{"bold":true}}' --format-fields textFormat.bold --json >/dev/null
run_required "sheets" "sheets clear" gog sheets clear "$sheet_id" "Sheet1!A1:B3" --json >/dev/null
export_path="$LIVE_TMP/sheets-export-$TS.xlsx"
run_required "sheets" "sheets export" gog sheets export "$sheet_id" --format xlsx --out "$export_path" >/dev/null
copy_json=$(gog sheets copy "$sheet_id" "gogcli-smoke-sheet-copy-$TS" --json)
copy_id=$(extract_id "$copy_json")
[ -n "$copy_id" ] || { echo "Failed to parse sheet copy id" >&2; exit 1; }
run_required "sheets" "drive delete sheet copy" gog drive delete "$copy_id" --force >/dev/null
run_required "sheets" "drive delete sheet" gog drive delete "$sheet_id" --force >/dev/null
}

View File

@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
run_slides_tests() {
if skip "slides"; then
echo "==> slides (skipped)"
return 0
fi
local slides_json slides_id copy_json copy_id export_path
slides_json=$(gog slides create "gogcli-smoke-slides-$TS" --json)
slides_id=$(extract_id "$slides_json")
[ -n "$slides_id" ] || { echo "Failed to parse slides id" >&2; exit 1; }
run_required "slides" "slides info" gog slides info "$slides_id" --json >/dev/null
export_path="$LIVE_TMP/slides-export-$TS.pdf"
run_required "slides" "slides export" gog slides export "$slides_id" --format pdf --out "$export_path" >/dev/null
copy_json=$(gog slides copy "$slides_id" "gogcli-smoke-slides-copy-$TS" --json)
copy_id=$(extract_id "$copy_json")
[ -n "$copy_id" ] || { echo "Failed to parse slides copy id" >&2; exit 1; }
run_required "slides" "drive delete slides copy" gog drive delete "$copy_id" --force >/dev/null
run_required "slides" "drive delete slides" gog drive delete "$slides_id" --force >/dev/null
}

View File

@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail
run_tasks_tests() {
if skip "tasks"; then
echo "==> tasks (skipped)"
return 0
fi
run_required "tasks" "tasks lists list" gog tasks lists list --json --max 1 >/dev/null
local list_json list_id
list_json=$(gog tasks lists list --json --max 1)
list_id=$(extract_tasklist_id "$list_json")
[ -n "$list_id" ] || { echo "No task list found" >&2; exit 1; }
run_required "tasks" "tasks list" gog tasks list "$list_id" --json --max 1 >/dev/null
local task_json task_id
task_json=$(gog tasks add "$list_id" --title "gogcli-smoke-$TS" --due "$DAY1" --json)
task_id=$(extract_id "$task_json")
[ -n "$task_id" ] || { echo "Failed to parse task id" >&2; exit 1; }
run_required "tasks" "tasks get" gog tasks get "$list_id" "$task_id" --json >/dev/null
run_required "tasks" "tasks update" gog tasks update "$list_id" "$task_id" --title "gogcli-smoke-updated-$TS" --json >/dev/null
run_required "tasks" "tasks done" gog tasks done "$list_id" "$task_id" --json >/dev/null
run_required "tasks" "tasks undo" gog tasks undo "$list_id" "$task_id" --json >/dev/null
run_required "tasks" "tasks delete" gog tasks delete "$list_id" "$task_id" --force >/dev/null
local repeat_json repeat_ids
repeat_json=$(gog tasks add "$list_id" --title "gogcli-smoke-repeat-$TS" --due "$DAY1" --repeat daily --repeat-count 2 --json)
repeat_ids=$(extract_task_ids "$repeat_json")
[ -n "$repeat_ids" ] || { echo "Failed to parse repeat task ids" >&2; exit 1; }
while IFS= read -r tid; do
[ -n "$tid" ] && run_required "tasks" "tasks delete repeat" gog tasks delete "$list_id" "$tid" --force >/dev/null
done <<<"$repeat_ids"
local done_json done_id
done_json=$(gog tasks add "$list_id" --title "gogcli-smoke-done-$TS" --due "$DAY1" --json)
done_id=$(extract_id "$done_json")
[ -n "$done_id" ] || { echo "Failed to parse done task id" >&2; exit 1; }
run_required "tasks" "tasks done (for clear)" gog tasks done "$list_id" "$done_id" --json >/dev/null
run_required "tasks" "tasks clear" gog --force tasks clear "$list_id" --json >/dev/null
}

View File

@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
run_workspace_tests() {
run_optional "groups" "groups list" gog groups list --json --max 5 >/dev/null
if [ -n "${GOG_LIVE_GROUP_EMAIL:-}" ]; then
run_optional "groups" "groups members" gog groups members "$GOG_LIVE_GROUP_EMAIL" --json --max 5 >/dev/null
fi
if skip "keep"; then
echo "==> keep (skipped)"
return 0
fi
if [ -z "${GOG_KEEP_SERVICE_ACCOUNT:-}" ] || [ -z "${GOG_KEEP_IMPERSONATE:-}" ]; then
if [ "${STRICT:-false}" = true ]; then
echo "Missing GOG_KEEP_SERVICE_ACCOUNT/GOG_KEEP_IMPERSONATE for keep tests." >&2
return 1
fi
echo "==> keep (optional; set GOG_KEEP_SERVICE_ACCOUNT and GOG_KEEP_IMPERSONATE)"
return 0
fi
run_optional "keep" "keep list" gog keep list --service-account "$GOG_KEEP_SERVICE_ACCOUNT" --impersonate "$GOG_KEEP_IMPERSONATE" --json --max 5 >/dev/null
run_optional "keep" "keep search" gog keep search "gogcli" --service-account "$GOG_KEEP_SERVICE_ACCOUNT" --impersonate "$GOG_KEEP_IMPERSONATE" --json >/dev/null
}