← back to George Gmail
test-draftid-exempt.sh
62 lines
#!/bin/bash
# TK-11552 negative test — proves the drain exempts a draft pinned by its STABLE DRAFT id.
#
# Why this exists: the keep-list matched only d.message.id, but Gmail ROTATES message.id on
# every draft save while the draft id is stable (proven live: 1a09ba5cc706157b -> 1a09ba5ce13ee153
# across one edit of draft r-5517420768746463086). So editing a protected draft silently dropped
# its protection and the 04:15 drain could permanently delete it.
#
# A positive-only test would prove nothing here — case B is the point: with the pin REMOVED the
# same draft MUST become deletable, or the test isn't measuring exemption at all.
# DRY RUN ONLY. CONFIRM is never set, so this script cannot delete anything.
set -uo pipefail
cd "$(dirname "$0")"
pass=0; fail=0
ck(){ if [ "$2" = "$3" ]; then echo " PASS $1 ($2)"; pass=$((pass+1)); else echo " FAIL $1 — expected '$3', got '$2'"; fail=$((fail+1)); fi; }
TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
# Pick a REAL live draft so we test the real matcher against real data, not a fixture shape.
read -r MSG DRAFT < <(node -e '
const fs=require("fs");
const t=fs.readFileSync(process.env.HOME+"/Projects/Designer-Wallcoverings/DW-MCP/.env","utf8");
let u="admin",p="";const m=t.match(/^GEORGE_BASIC_AUTH=(.+)$/m);
if(m){const v=m[1].trim();if(v.includes(":")){const s=v.split(":");u=s[0];p=s.slice(1).join(":");}}
if(!p){const mp=t.match(/^GEORGE_BASIC_AUTH_PASS=(.+)$/m);if(mp)p=mp[1].trim();}
const H={Authorization:"Basic "+Buffer.from(u+":"+p).toString("base64")};
fetch("http://127.0.0.1:9850/api/drafts?maxResults=500&account=info",{headers:H}).then(r=>r.json()).then(j=>{
const ds=j.drafts||j.items||(Array.isArray(j)?j:[]);
const d=ds.find(x=>x.message&&x.message.id&&x.id);
if(!d){console.error("no draft available");process.exit(1);}
console.log(d.message.id, d.id);});') || { echo "SETUP FAILED — could not read a live draft"; exit 1; }
if [ -z "${MSG:-}" ] || [ -z "${DRAFT:-}" ]; then echo "SETUP FAILED — NOT_MEASURED (no ids)"; exit 1; fi
echo "fixture: message=$MSG draft=$DRAFT"
printf "[\"%s\"]" "$MSG" > "$TMP/set.json" # pretend this draft is >30d
run(){
local out="$TMP/out.$$.txt"
SET="$TMP/set.json" DRAIN_KEEP_LIST="$1" node delete-old-drafts-info.js >"$out" 2>&1
local rc=$?
# A crashed run prints no count; without this guard an empty result would look like a
# measurement instead of a failure (the rc=127 false-PASS trap).
if [ $rc -ne 0 ]; then echo "RUN_FAILED_rc=$rc"; sed -n '1,5p' "$out" >&2; return; fi
local n
n=$(sed -n 's/.*deletable in first 500 window: \([0-9][0-9]*\).*/\1/p' "$out" | head -1)
if [ -z "$n" ]; then echo "NOT_MEASURED"; sed -n '1,8p' "$out" >&2; return; fi
echo "$n"
}
echo '{}' > "$TMP/empty.json"
printf '{"%s":"draft-id pin"}' "$DRAFT" > "$TMP/bydraft.json"
printf '{"%s":"message-id pin"}' "$MSG" > "$TMP/bymsg.json"
echo "A. pinned by DRAFT id (the fix) -> must be exempt"
ck "draft-id pin exempts" "$(run "$TMP/bydraft.json")" "0"
echo "B. NO pin (injected fault) -> must be deletable, or the test measures nothing"
ck "unpinned is deletable" "$(run "$TMP/empty.json")" "1"
echo "C. pinned by MESSAGE id (legacy path) -> must still be exempt"
ck "message-id pin exempts" "$(run "$TMP/bymsg.json")" "0"
echo; echo "passed=$pass failed=$fail"; [ "$fail" -eq 0 ] || exit 1