- rdd_test_plan.md : plan global, 227 cas, 33 sections - TC-01 : infrastructure coeur (15/15) - TC-02 : acteur personnage (7/7) - TC-03 : acteur creature (11/11) - TC-04 : acteur entite + subtypes + possession (14/14) - TC-05 : acteur vehicule + structure/resistance (10/10) - TC-06 : acteur commerce + achat/vente simules (14/14) Scripts atomiques, async polling, page-state race-free
225 lines
11 KiB
Bash
Executable File
225 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# TC-06 — Acteur Commerce
|
|
# Usage: ./tc-06-acteur-commerce.sh [--navigate]
|
|
# --navigate : force navigation + login
|
|
# Env: URL, PASS (admin password)
|
|
#
|
|
# Atomic single evaluate_script: no cross-call races.
|
|
# ============================================================================
|
|
set -uo pipefail
|
|
|
|
URL="${URL:-https://localhost:31000}"
|
|
PASS="${PASS:-}"
|
|
NAVIGATE="${1:-}"
|
|
PASSED=0; FAILED=0
|
|
|
|
die() { echo " ✖ $*"; exit 1; }
|
|
pass() { echo " ✔ $1"; PASSED=$((PASSED+1)); }
|
|
fail() { echo " ✘ $1"; FAILED=$((FAILED+1)); }
|
|
|
|
jse_get() { chrome-devtools evaluate_script "() => $1" 2>/dev/null | sed -n '/^```json$/{n;p;}'; }
|
|
|
|
jse_wait_ready() {
|
|
local t="${1:-60}" p="${2:-1000}"
|
|
chrome-devtools evaluate_script "async () => {
|
|
for(let i=0;i<$t*1000/$p;i++){ if(typeof game!=='undefined'&&game.ready) return 'ready'; await new Promise(r=>setTimeout(r,$p)); }
|
|
return 'timeout';
|
|
}" 2>/dev/null | sed -n '/^```json$/{n;p;}' | tr -d '"\n'
|
|
}
|
|
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════════════╗"
|
|
echo "║ TC-06 — Acteur Commerce ║"
|
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
R=$(jse_wait_ready 15 2)
|
|
if [ "$R" != "ready" ] && [ "$NAVIGATE" = "--navigate" ]; then
|
|
[ -z "$PASS" ] && die "PASS=..."
|
|
echo "→ Navigating ..."
|
|
chrome-devtools new_page "$URL" --background false --timeout 15000 --isolatedContext "rdd-tc06" >/dev/null 2>&1; sleep 3
|
|
PW=$(jse_get "window.location.pathname" | tr -d '"\n')
|
|
case "$PW" in
|
|
*/join) chrome-devtools evaluate_script "() => {
|
|
const s=document.querySelector('select[name=userid]'); if(s){ const o=Array.from(s.options); const g=o.find(x=>x.text==='Gamemaster'); const p=g||o.find(x=>x.value&&!x.disabled)||o.find(x=>x.value); if(p) s.value=p.value; }
|
|
const a=document.querySelector('input[name=adminPassword]'); if(a) a.value='$PASS';
|
|
const b=document.querySelector('button[name=join]'); if(b) b.click();
|
|
}" >/dev/null 2>&1 || true ;;
|
|
*/setup) chrome-devtools evaluate_script "() => {
|
|
document.querySelectorAll('[data-action=worldLaunch]').forEach(l=>{ const li=l.closest('li.package.world'); if(li&&li.querySelector('.package-title')?.textContent==='Reve de Dragon') l.click(); });
|
|
}" >/dev/null 2>&1 || true ;;
|
|
*/auth) chrome-devtools evaluate_script "() => {
|
|
const i=document.querySelector('input[type=password]'); if(i) i.value='$PASS';
|
|
const b=document.querySelector('form button,button[type=submit]'); if(b) b.click();
|
|
}" >/dev/null 2>&1 || true ;;
|
|
esac
|
|
echo "→ Waiting..."; R=$(jse_wait_ready 60 2)
|
|
fi
|
|
|
|
[ "$R" != "ready" ] && die "Game not ready"
|
|
echo " ✓ Game ready"; echo ""
|
|
|
|
# ============================================================================
|
|
# Single atomic evaluate_script: all sections in one call
|
|
# ============================================================================
|
|
RESULT=$(chrome-devtools evaluate_script "async () => {
|
|
if (typeof game === 'undefined' || !game.ready) return {error: 'NOT_READY'};
|
|
const out = {};
|
|
|
|
// ==============================
|
|
// Cleanup any previous TC-06 actors
|
|
// ==============================
|
|
for (const n of ['TC-06 Marchand', 'TC-06 Acheteur']) {
|
|
const a = game.actors.getName(n);
|
|
if (a) await a.delete();
|
|
}
|
|
|
|
// ==============================
|
|
// TC-06-001: Commerce creation
|
|
// ==============================
|
|
try {
|
|
const [actor] = await Actor.createDocuments([{type: 'commerce', name: 'TC-06 Marchand'}]);
|
|
await actor.createEmbeddedDocuments('Item', [
|
|
{name: 'Denier (etain)', type: 'monnaie', system: {cout: 0.01, quantite: 0}},
|
|
{name: 'Sou (bronze)', type: 'monnaie', system: {cout: 0.05, quantite: 0}},
|
|
{name: 'Sol (argent)', type: 'monnaie', system: {cout: 1, quantite: 0}},
|
|
{name: 'Dragon (or)', type: 'monnaie', system: {cout: 240, quantite: 10}}
|
|
]);
|
|
const sheet = await actor.sheet.render(true);
|
|
await new Promise(r => setTimeout(r, 800));
|
|
out.sheetName = sheet.constructor.name;
|
|
await sheet.close();
|
|
out.monnaieCount = actor.items.filter(i => i.type === 'monnaie').length;
|
|
out.pourcentage = actor.system.pourcentage;
|
|
out.c001_1 = (out.sheetName === 'RdDCommerceSheetV2');
|
|
out.c001_2 = (out.monnaieCount >= 4);
|
|
out.c001_3 = (out.pourcentage === 100);
|
|
out.commerceId = actor.id;
|
|
} catch(e) { out.c001_error = e?.message || String(e); }
|
|
|
|
// ==============================
|
|
// TC-06-002: Purchase simulation
|
|
// ==============================
|
|
try {
|
|
const commerce = game.actors.get(out.commerceId);
|
|
if (!commerce) { out.c002_error = 'Commerce not found'; }
|
|
else {
|
|
const [item] = await commerce.createEmbeddedDocuments('Item', [{
|
|
name: 'Epee longue', type: 'objet',
|
|
system: { cout: 50, quantite: 3, encombrement: 2 }
|
|
}]);
|
|
out.prixCalcule = commerce.calculerPrix(item);
|
|
out.quantiteDispo = commerce.getQuantiteDisponible(item);
|
|
|
|
const [buyer] = await Actor.createDocuments([{type: 'personnage', name: 'TC-06 Acheteur'}]);
|
|
await buyer.createEmbeddedDocuments('Item', [{
|
|
name: 'Dragon (or)', type: 'monnaie', system: { cout: 240, quantite: 5 }
|
|
}]);
|
|
|
|
out.peutPayer = buyer.verifierFortune(out.prixCalcule);
|
|
const reste = await buyer.depenserSols(out.prixCalcule);
|
|
out.resteApres = reste;
|
|
out.c002_payeurResteOk = (reste >= 0);
|
|
|
|
await commerce.ajouterSols(out.prixCalcule);
|
|
await commerce.decrementerQuantiteItem(item.id, 1);
|
|
await buyer.createEmbeddedDocuments('Item', [{
|
|
name: item.name, type: item.type,
|
|
system: { cout: item.system.cout, quantite: 1, encombrement: item.system.encombrement }
|
|
}]);
|
|
|
|
out.itemTransfere = buyer.items.filter(i => i.name === 'Epee longue').length > 0;
|
|
out.c002_1 = (out.prixCalcule === 50);
|
|
out.c002_2 = (out.quantiteDispo === 3);
|
|
out.c002_3 = (out.peutPayer === true);
|
|
out.c002_4 = (out.c002_payeurResteOk === true);
|
|
out.c002_5 = (!(reste >= 0) || (item.system.cout === 50 && out.c002_payeurResteOk));
|
|
out.c002_6 = (out.itemTransfere === true);
|
|
}
|
|
} catch(e) { out.c002_error = e?.message || String(e); }
|
|
|
|
// ==============================
|
|
// TC-06-003: Sell simulation
|
|
// ==============================
|
|
try {
|
|
const commerce = game.actors.getName('TC-06 Marchand');
|
|
const buyer = game.actors.getName('TC-06 Acheteur');
|
|
if (!commerce || !buyer) { out.c003_error = 'actors not found for TC-06-003'; }
|
|
else {
|
|
const [item] = await buyer.createEmbeddedDocuments('Item', [{
|
|
name: 'Potion de soin', type: 'potion',
|
|
system: { cout: 30, quantite: 2, encombrement: 1 }
|
|
}]);
|
|
out.prixItem = item.calculerPrixCommercant();
|
|
out.prixVente = Math.floor(out.prixItem / 2);
|
|
out.commerceSuffisant = commerce.verifierFortune(out.prixVente);
|
|
const fortuneVendeurAvant = buyer.getFortune();
|
|
const depenseOk = await commerce.depenserSols(out.prixVente);
|
|
await buyer.ajouterSols(out.prixVente);
|
|
const fortuneVendeurApres = buyer.getFortune();
|
|
out.gainReel = fortuneVendeurApres - fortuneVendeurAvant;
|
|
|
|
out.c003_1 = (typeof out.prixItem === 'number' && out.prixItem > 0);
|
|
out.c003_2 = (typeof out.prixVente === 'number' && out.prixVente > 0);
|
|
out.c003_3 = (out.commerceSuffisant === true);
|
|
out.c003_4 = (out.gainReel === out.prixVente);
|
|
}
|
|
} catch(e) { out.c003_error = e?.message || String(e); }
|
|
|
|
// ==============================
|
|
// Cleanup
|
|
// ==============================
|
|
for (const n of ['TC-06 Marchand', 'TC-06 Acheteur']) {
|
|
const a = game.actors.getName(n);
|
|
if (a) await a.delete();
|
|
}
|
|
out.cleaned = true;
|
|
|
|
return out;
|
|
}" 2>/dev/null | sed -n '/^```json$/{n;p;}')
|
|
|
|
[ -z "$RESULT" ] && die "Empty result"
|
|
eval_ok() { echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
|
|
|
|
echo "--- TC-06-001: Commerce creation + sheet ---"
|
|
[ "$(eval_ok c001_1)" = "True" ] && pass "TC-06-001a: Sheet $(eval_ok sheetName)" || fail "TC-06-001a: Sheet = $(eval_ok sheetName) (expected RdDCommerceSheetV2)"
|
|
[ "$(eval_ok c001_2)" = "True" ] && pass "TC-06-001b: $(eval_ok monnaieCount) monnaies" || fail "TC-06-001b: monnaies = $(eval_ok monnaieCount) (expected ≥4)"
|
|
[ "$(eval_ok c001_3)" = "True" ] && pass "TC-06-001c: pourcentage = $(eval_ok pourcentage)" || fail "TC-06-001c: pourcentage = $(eval_ok pourcentage)"
|
|
[ -n "$(eval_ok c001_error)" ] && fail "TC-06-001: $(eval_ok c001_error)"
|
|
|
|
echo ""
|
|
echo "--- TC-06-002: Achat (simulation) ---"
|
|
[ "$(eval_ok c002_1)" = "True" ] && pass "TC-06-002a: prix = $(eval_ok prixCalcule) sols" || fail "TC-06-002a: prix = $(eval_ok prixCalcule)"
|
|
[ "$(eval_ok c002_2)" = "True" ] && pass "TC-06-002b: quantite = $(eval_ok quantiteDispo)" || fail "TC-06-002b: quantite = $(eval_ok quantiteDispo)"
|
|
[ "$(eval_ok c002_3)" = "True" ] && pass "TC-06-002c: peut payer $(eval_ok prixCalcule) sols" || fail "TC-06-002c: verifierFortune = $(eval_ok peutPayer)"
|
|
[ "$(eval_ok c002_4)" = "True" ] && pass "TC-06-002d: depenserSols (reste=$(eval_ok resteApres))" || fail "TC-06-002d: depenserSols returned $(eval_ok resteApres)"
|
|
[ "$(eval_ok c002_6)" = "True" ] && pass "TC-06-002e: item transfere" || fail "TC-06-002e: item transfere = $(eval_ok itemTransfere)"
|
|
[ -n "$(eval_ok c002_error)" ] && fail "TC-06-002: $(eval_ok c002_error)"
|
|
|
|
echo ""
|
|
echo "--- TC-06-003: Vente (simulation) ---"
|
|
[ "$(eval_ok c003_1)" = "True" ] && pass "TC-06-003a: prix item = $(eval_ok prixItem) sols" || fail "TC-06-003a: prix item = $(eval_ok prixItem)"
|
|
[ "$(eval_ok c003_2)" = "True" ] && pass "TC-06-003b: prix vente = $(eval_ok prixVente) sols" || fail "TC-06-003b: prix vente = $(eval_ok prixVente)"
|
|
[ "$(eval_ok c003_3)" = "True" ] && pass "TC-06-003c: commerce peut payer $(eval_ok prixVente)" || fail "TC-06-003c: commerceSuffisant = $(eval_ok commerceSuffisant)"
|
|
GE=$(eval_ok gainReel); PV=$(eval_ok prixVente)
|
|
[ -n "$GE" ] && [ "$GE" -ge "$PV" ] 2>/dev/null && pass "TC-06-003d: vendeur recoit $GE sols (prix vente $PV)" || fail "TC-06-003d: gain = $GE (expected >= $PV)"
|
|
[ -n "$(eval_ok c003_error)" ] && fail "TC-06-003: $(eval_ok c003_error)"
|
|
|
|
echo ""
|
|
echo "--- Cleanup ---"
|
|
[ "$(eval_ok cleaned)" = "True" ] && pass "Cleanup: done" || fail "Cleanup: not run"
|
|
|
|
echo ""
|
|
echo "--- Console errors ---"
|
|
ERRS=$(chrome-devtools list_console_messages --types error 2>/dev/null | grep -c '^msgid=' || true)
|
|
[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS"
|
|
|
|
TOTAL=$((PASSED + FAILED))
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════════════╗"
|
|
printf "║ %2d / %2d passed, %2d failed ║\n" "$PASSED" "$TOTAL" "$FAILED"
|
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
[ "$FAILED" -gt 0 ] && exit 1 || exit 0
|