From a0d51c7e42b32749a81b2edf0dd95da2b22050f9 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 20 Jul 2026 16:07:07 +0000
Subject: [PATCH] =?UTF-8?q?refactor(console):=20de-mock=20HITL/Ordens/Usu?=
=?UTF-8?q?=C3=A1rios=20+=20migra=20specs=20(limpeza=20de=20mocks=205.4)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Quinta fatia (strangler). Remove as branches de mock de PRODUÇÃO de 3 telas e migra
rbac.spec + pair_dimension.spec para o fixture. Coexistência mantida (o core sai na
5.7). Diff 100% em docs/design/pages/.
Telas de-mockadas:
- screen_hitl, screen_orders, screen_users (removido const mock / if(mock) /
blocos de mock-data CT.*).
Vazamento latente religado (só o HITL): no render real → order.confidence_breakdown (por ordem,
vindo de /v1/orders — coerente com o Fix #3 que pôs a confiança no payload).
Ordens/Usuários sem vazamento (o myEmail de Users é auth, preservado).
Gating de demo PRESERVADO (separado do mock): no HITL, canApprove/demoView/
demoAutonomy + aviso "Somente leitura…" + botões disabled com data-tip ficam
intactos — só os short-circuits if(mock) saíram. (rbac test 2 afirma esses tooltips.)
Fixture (e2e/fixtures/):
- HITL_CONFIG enriquecido ao shape completo (current_level, human_approved/rejected_
today, levels[]). ORDERS (pendentes, BTC+ETH, confidence + confidence_breakdown),
TRADES (fechados, ≥2 símbolos), USERS (exatamente 3 — rbac test 3), ROLES (com
permissions p/ a matriz). /v1/orders no baseline passa de [] para ORDERS.
Specs migrados: rbac.spec (cenários via installMockApi authMode/role, não mais
MOCK_ROLE/MOCK_AUTH), pair_dimension.spec.
Acoplamento corrigido (mesmo padrão da 5.3b): system.spec "admin reaches the users
screen" navega #users e afirma o page-title → Users de-mockada precisa do fixture;
instalado NESSE teste (serve /v1/users+/v1/roles; USE_MOCK_DATA segue p/ a auth mock).
403/404 do system são route-guard (intactos); console afirma só labels de nav.
Auditoria de mocks: 3 telas sem USE_MOCK_DATA/CT.* (grep limpo). Restam: core+shell+
landing (5.7), Agentes (5.6), grupos 5.5–5.6.
Validação: 11 specs migrados verdes (rbac 4 + pair_dimension 3 + system 4); suíte
completa 75 verdes (retries=1); node build.mjs OK.
Co-Authored-By: Claude Opus 4.8
Claude-Session: https://claude.ai/code/session_01UZ3vLNTHKekjQVtjFVRi5D
---
docs/design/pages/e2e/fixtures/datasets.js | 53 +++++++++++++++++-
docs/design/pages/e2e/fixtures/mockApi.js | 10 ++--
docs/design/pages/e2e/pair_dimension.spec.js | 3 +-
docs/design/pages/e2e/rbac.spec.js | 19 +++----
docs/design/pages/e2e/system.spec.js | 3 ++
docs/design/pages/screen_hitl.jsx | 56 +++-----------------
docs/design/pages/screen_orders.jsx | 25 ++-------
docs/design/pages/screen_users.jsx | 11 ++--
8 files changed, 84 insertions(+), 96 deletions(-)
diff --git a/docs/design/pages/e2e/fixtures/datasets.js b/docs/design/pages/e2e/fixtures/datasets.js
index 848c60b..365e43f 100644
--- a/docs/design/pages/e2e/fixtures/datasets.js
+++ b/docs/design/pages/e2e/fixtures/datasets.js
@@ -236,8 +236,57 @@ export const MARKET_SIGNAL = {
valid_until: new Date(Date.now() + 3600e3).toISOString(), as_of: new Date().toISOString(),
};
-// GET /v1/hitl/config — the header's autonomy badge.
-export const HITL_CONFIG = { level: 1, threshold_usdt: 100.0, label: "Assistido" };
+// GET /v1/hitl/config — header autonomy badge + the HITL Controls screen.
+export const HITL_CONFIG = {
+ level: 1, current_level: 1, threshold_usdt: 100.0, label: "Assistido",
+ level_description: "Ordens acima do limite exigem aprovação humana.",
+ pending_orders_count: 2, human_approved_today: 5, human_rejected_today: 1,
+ levels: [
+ { level: 0, threshold_usdt: 0, description: "Manual — toda ordem exige aprovação." },
+ { level: 1, threshold_usdt: 100, description: "Assistido — ordens acima do limite exigem aprovação." },
+ { level: 2, threshold_usdt: 500, description: "Semi-autônomo — só ordens grandes exigem aprovação." },
+ { level: 3, threshold_usdt: 999999, description: "Autônomo — sem aprovação (confirmação extra)." },
+ ],
+};
+
+// GET /v1/orders — pending queue (HITL) + Orders table. ≥2 pares incl. BTC/USDT
+// para os chips de par (N4); confidence (Fix #3) + confidence_breakdown por ordem.
+export const ORDERS = [
+ { id: "ord_a1", pair: "BTC/USDT", side: "buy", quantity: 0.03, price: 64500, entry: 64500,
+ stop_loss: 63200, take_profit: 66800, position_size_pct: 2, confidence: 0.82, status: "pending",
+ strategy: "trend_following", agent_id: "strategy_agent", reason: "Rompimento confirmado por volume.",
+ created_at: new Date().toISOString(),
+ confidence_breakdown: [{ key: "trend", score: 0.85 }, { key: "momentum", score: 0.7 }, { key: "volume", score: 0.8 }] },
+ { id: "ord_b2", pair: "ETH/USDT", side: "buy", quantity: 0.5, price: 3200, entry: 3200,
+ stop_loss: 3080, take_profit: 3450, position_size_pct: 2, confidence: 0.71, status: "pending",
+ strategy: "mean_reversion", agent_id: "strategy_agent", reason: "Reversão no suporte confirmada.",
+ created_at: new Date().toISOString(),
+ confidence_breakdown: [{ key: "trend", score: 0.6 }, { key: "momentum", score: 0.75 }, { key: "volume", score: 0.8 }] },
+];
+
+// GET /v1/trades — trades fechados (P&L realizado por par, N5). ≥2 símbolos incl. BTC/USDT.
+export const TRADES = [
+ { order_id: "t1", symbol: "BTC/USDT", side: "buy", entry_price: 61200, exit_price: 64810, quantity: 0.03, fee: 0.5, pnl: 108.30, pnl_pct: 5.9 },
+ { order_id: "t2", symbol: "BTC/USDT", side: "buy", entry_price: 65000, exit_price: 63600, quantity: 0.03, fee: 0.5, pnl: -42.10, pnl_pct: -2.1 },
+ { order_id: "t3", symbol: "ETH/USDT", side: "buy", entry_price: 3100, exit_price: 3223, quantity: 0.5, fee: 0.3, pnl: 61.50, pnl_pct: 4.0 },
+ { order_id: "t4", symbol: "SOL/USDT", side: "buy", entry_price: 150, exit_price: 160.3, quantity: 1.2, fee: 0.2, pnl: 12.38, pnl_pct: 1.5 },
+ { order_id: "t5", symbol: "SOL/USDT", side: "buy", entry_price: 148, exit_price: 168.7, quantity: 1.2, fee: 0.2, pnl: 24.90, pnl_pct: 3.0 },
+ { order_id: "t6", symbol: "XRP/USDT", side: "buy", entry_price: 0.62, exit_price: 0.60, quantity: 400, fee: 0.1, pnl: -8.20, pnl_pct: -1.3 },
+];
+
+// GET /v1/users — exatamente 3 (rbac test 3 afirma tbody tr === 3).
+export const USERS = [
+ { id: "u_demo", name: "Operador Demo", email: "demo@criptotrade.dev", role: "admin", status: "active", last_login_at: new Date().toISOString(), invite_id: null },
+ { id: "u_ana", name: "Ana", email: "ana@criptotrade.dev", role: "operador", status: "active", last_login_at: new Date(Date.now() - 86400e3).toISOString(), invite_id: null },
+ { id: "u_novo", name: null, email: "novo@criptotrade.dev", role: "visualizador", status: "pending", last_login_at: null, invite_id: "inv_1" },
+];
+
+// GET /v1/roles — a "Matriz de permissões" renderiza de r.permissions.
+export const ROLES = [
+ { id: "admin", label: "Admin", permissions: ["approve_order", "change_autonomy", "change_risk", "edit_settings", "manage_keys", "view_audit", "manage_users"] },
+ { id: "operador", label: "Operador", permissions: ["approve_order", "change_autonomy", "view_audit"] },
+ { id: "visualizador", label: "Visualizador", permissions: [] },
+];
// GET /v1/market/{pair}/ticker — the header price (when a single pair is selected).
export function ticker(pair) {
diff --git a/docs/design/pages/e2e/fixtures/mockApi.js b/docs/design/pages/e2e/fixtures/mockApi.js
index 4257887..563ae89 100644
--- a/docs/design/pages/e2e/fixtures/mockApi.js
+++ b/docs/design/pages/e2e/fixtures/mockApi.js
@@ -15,8 +15,9 @@ import {
AGENTS, authMe, candles, deskSummary, EQUITY, HEALTH, HITL_CONFIG, MARKET_INDICATORS,
JOURNAL, JOURNAL_METRICS, MARKET_LEVELS, MARKET_PATTERNS, MARKET_REGIME, MARKET_SIGNAL,
MARKET_VOLUME_PROFILE,
- metrics, ONBOARDING, PAIRS_FLAT, PAIRS_RICH, processEvents, RISK_CIRCUIT_BREAKER,
- RISK_CONFIG, RISK_KELLY, RISK_PROTECTIONS, RISK_SKIPS, RISK_SLOTS, SYS_CONFIG, ticker,
+ metrics, ONBOARDING, ORDERS, PAIRS_FLAT, PAIRS_RICH, processEvents, RISK_CIRCUIT_BREAKER,
+ RISK_CONFIG, RISK_KELLY, RISK_PROTECTIONS, RISK_SKIPS, RISK_SLOTS, ROLES, SYS_CONFIG,
+ ticker, TRADES, USERS,
} from "./datasets.js";
// Wrap a payload in the API envelope the client's req() unwraps ({ data, meta }).
@@ -41,7 +42,10 @@ function baseline({ authMode, role }) {
"GET /v1/metrics/equity": () => EQUITY,
"GET /v1/onboarding/status": () => ONBOARDING,
"GET /v1/desk/summary": () => deskSummary(),
- "GET /v1/orders": () => [], // sidebar pending-count poll → empty
+ "GET /v1/orders": () => ORDERS, // sidebar poll + HITL queue + Ordens
+ "GET /v1/trades": () => TRADES, // Ordens: P&L realizado por par (N5)
+ "GET /v1/users": () => USERS, // Usuários & Permissões (exatamente 3)
+ "GET /v1/roles": () => ROLES, // matriz de permissões
// Risco (screen_risk)
"GET /v1/risk/protections": () => RISK_PROTECTIONS,
"GET /v1/risk/circuit-breaker": () => RISK_CIRCUIT_BREAKER,
diff --git a/docs/design/pages/e2e/pair_dimension.spec.js b/docs/design/pages/e2e/pair_dimension.spec.js
index 2740df9..74c65d8 100644
--- a/docs/design/pages/e2e/pair_dimension.spec.js
+++ b/docs/design/pages/e2e/pair_dimension.spec.js
@@ -2,9 +2,10 @@
// N4: HITL ganha mini-contexto do par (preço/regime) + filtro por par na fila.
// N5: Ordens ganham o rodapé de P&L realizado por par no modo ∑.
import { test, expect } from "@playwright/test";
+import { installMockApi } from "./fixtures/mockApi.js";
test.beforeEach(async ({ page }) => {
- await page.addInitScript(() => { window.USE_MOCK_DATA = true; });
+ await installMockApi(page, { authMode: "user" });
});
test("N4 — HITL: cada ordem mostra o mini-contexto do par (preço atual)", async ({ page }) => {
diff --git a/docs/design/pages/e2e/rbac.spec.js b/docs/design/pages/e2e/rbac.spec.js
index 71c422f..98b7b04 100644
--- a/docs/design/pages/e2e/rbac.spec.js
+++ b/docs/design/pages/e2e/rbac.spec.js
@@ -1,12 +1,11 @@
// A3 RBAC in the console: role-driven visibility (visualizador hides actions,
// demo shows them disabled with the discovery tooltip, admin sees everything).
+// Cenários de auth/role vêm do fixture (/v1/auth/me), não mais de MOCK_ROLE/MOCK_AUTH.
import { test, expect } from "@playwright/test";
+import { installMockApi } from "./fixtures/mockApi.js";
test("visualizador sees no approve/reject and no admin nav", async ({ page }) => {
- await page.addInitScript(() => {
- window.USE_MOCK_DATA = true;
- window.MOCK_ROLE = "visualizador";
- });
+ await installMockApi(page, { authMode: "user", role: "visualizador" });
await page.goto("/#hitl");
await expect(page.locator(".page-title")).toContainText("HITL");
await expect(page.getByRole("button", { name: "Aprovar" })).toHaveCount(0);
@@ -18,10 +17,7 @@ test("visualizador sees no approve/reject and no admin nav", async ({ page }) =>
});
test("demo mode shows actions disabled with the discovery tooltip", async ({ page }) => {
- await page.addInitScript(() => {
- window.USE_MOCK_DATA = true;
- window.MOCK_AUTH = "demo";
- });
+ await installMockApi(page, { authMode: "demo" });
await page.goto("/#hitl");
const approve = page.getByRole("button", { name: "Aprovar" }).first();
await expect(approve).toBeVisible();
@@ -34,7 +30,7 @@ test("demo mode shows actions disabled with the discovery tooltip", async ({ pag
});
test("admin sees the Administração group and the users screen", async ({ page }) => {
- await page.addInitScript(() => { window.USE_MOCK_DATA = true; });
+ await installMockApi(page, { authMode: "user", role: "admin" });
await page.goto("/");
await page.locator(".nav-item", { hasText: "Usuários & Permissões" }).click();
await expect(page).toHaveURL(/#users$/);
@@ -44,10 +40,7 @@ test("admin sees the Administração group and the users screen", async ({ page
});
test("operador keeps approve buttons but has no admin nav", async ({ page }) => {
- await page.addInitScript(() => {
- window.USE_MOCK_DATA = true;
- window.MOCK_ROLE = "operador";
- });
+ await installMockApi(page, { authMode: "user", role: "operador" });
await page.goto("/#hitl");
await expect(page.getByRole("button", { name: "Aprovar" }).first()).toBeEnabled();
await expect(page.locator(".nav-item", { hasText: "Usuários & Permissões" }))
diff --git a/docs/design/pages/e2e/system.spec.js b/docs/design/pages/e2e/system.spec.js
index 31b5e15..f62b1f3 100644
--- a/docs/design/pages/e2e/system.spec.js
+++ b/docs/design/pages/e2e/system.spec.js
@@ -25,6 +25,9 @@ test("route without permission shows 403 with the required permission", async ({
});
test("admin still reaches the users screen directly", async ({ page }) => {
+ // Users de-mockada (5.4) → serve /v1/users + /v1/roles pelo fixture; USE_MOCK_DATA
+ // segue só para a auth mock admin (o route-guard). Coexistência transitória.
+ await installMockApi(page, { authMode: "user", role: "admin" });
await page.addInitScript(() => { window.USE_MOCK_DATA = true; });
await page.goto("/#users");
await expect(page.locator(".page-title")).toContainText("Usuários & Permissões");
diff --git a/docs/design/pages/screen_hitl.jsx b/docs/design/pages/screen_hitl.jsx
index 7fae34a..407f272 100644
--- a/docs/design/pages/screen_hitl.jsx
+++ b/docs/design/pages/screen_hitl.jsx
@@ -97,7 +97,7 @@ function PendingOrderCard({ order, onDecide, highlight = false, ctx = null }) {
)}
-
+
{!canApprove && !demoView && (
({
- level: l.level,
- threshold_usdt: l.threshold,
- description: l.desc,
- })),
- };
- const mockOrders = CT.pendingOrders.map(o => ({
- ...o,
- stop_loss: o.stop,
- take_profit: o.takeProfit,
- position_size_pct: o.sizePct,
- }));
-
- const [config, setConfig] = useState(mock ? mockConfig : null);
- const [orders, setOrders] = useState(mock ? mockOrders : []);
- const [loading, setLoading] = useState(!mock);
+ const [config, setConfig] = useState(null);
+ const [orders, setOrders] = useState([]);
+ const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// M2: pair handed over by Mercado's "Ver no HITL" — highlight its orders once.
const [focusPair] = useState(() => {
@@ -199,24 +177,16 @@ function ScreenHITL({ addToast }) {
const [pairFilter, setPairFilter] = useState(focusPair);
// N4: per-pair context (preço atual + regime) from the desk snapshot — fetched
// ONCE here and distributed to the cards, never one request per order.
- const [deskCtx, setDeskCtx] = useState(() => mock ? {
- 'BTC/USDT': { last: 64810, regime_label: 'Alta forte' },
- 'ETH/USDT': { last: 3208, regime_label: 'Lateral' },
- 'SOL/USDT': { last: 160.42, regime_label: 'Alta forte' },
- 'XRP/USDT': { last: 0.61, regime_label: 'Desconhecido' },
- 'BNB/USDT': { last: 592, regime_label: 'Caótico' },
- } : {});
+ const [deskCtx, setDeskCtx] = useState(() => ({}));
useEffect(() => {
- if (mock) return;
CT_API.getDeskSummary().then(d => {
const map = {};
(d.rows || []).forEach(r => { map[r.symbol] = { last: r.last, regime_label: r.regime_label }; });
setDeskCtx(map);
}).catch(() => {});
- }, [mock]);
+ }, []);
const load = useCallback(() => {
- if (mock) return;
setLoading(true);
Promise.all([
CT_API.getHITL(),
@@ -228,15 +198,11 @@ function ScreenHITL({ addToast }) {
setLoading(false);
})
.catch(e => { setError(e); setLoading(false); });
- }, [mock]);
+ }, []);
useEffect(() => { load(); }, [load]);
const decide = async (orderId, action, note) => {
- if (mock) {
- setOrders(prev => prev.filter(o => o.id !== orderId));
- return;
- }
try {
// A3: no client-sent operator — the server stamps the session identity.
await CT_API.decideOrder(orderId, {
@@ -252,14 +218,6 @@ function ScreenHITL({ addToast }) {
};
const setLevel = async (level) => {
- if (mock) {
- setConfig(prev => ({
- ...prev,
- current_level: level,
- level_description: prev.levels[level]?.description ?? '',
- }));
- return;
- }
try {
// A3: no client-sent operator — the server stamps the session identity.
const updated = await CT_API.patchHITL({
diff --git a/docs/design/pages/screen_orders.jsx b/docs/design/pages/screen_orders.jsx
index 2e09e96..0ed692e 100644
--- a/docs/design/pages/screen_orders.jsx
+++ b/docs/design/pages/screen_orders.jsx
@@ -20,32 +20,15 @@ const ORDER_STATUS_LABEL = {
};
function ScreenOrders() {
- const mock = !!window.USE_MOCK_DATA;
- const mockOrders = CT.orders.map(o => ({
- ...o,
- stop_loss: o.stop,
- take_profit: o.takeProfit,
- position_size_pct: o.sizePct,
- operator_note: o.operatorNote,
- }));
-
- // N5: mock closed trades (realized P&L) for the per-pair footer.
- const mockTrades = [
- { symbol: 'BTC/USDT', pnl: 108.30 }, { symbol: 'BTC/USDT', pnl: -42.10 },
- { symbol: 'ETH/USDT', pnl: 61.50 }, { symbol: 'SOL/USDT', pnl: 12.38 },
- { symbol: 'SOL/USDT', pnl: 24.90 }, { symbol: 'XRP/USDT', pnl: -8.20 },
- ];
-
const [scope] = useCurrentPair();
- const [orders, setOrders] = useState(mock ? mockOrders : null);
- const [trades, setTrades] = useState(mock ? mockTrades : null);
- const [loading, setLoading] = useState(!mock);
+ const [orders, setOrders] = useState(null);
+ const [trades, setTrades] = useState(null);
+ const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [statusF, setStatusF] = useState('all');
const [sideF, setSideF] = useState('all');
useEffect(() => {
- if (mock) return;
setLoading(true);
const pairQ = scope && scope !== 'ALL' ? `&pair=${encodeURIComponent(scope)}` : '';
Promise.all([CT_API.getOrders(100, 0, pairQ), CT_API.getTrades(200, 0, pairQ).catch(() => [])])
@@ -55,7 +38,7 @@ function ScreenOrders() {
setLoading(false);
})
.catch(e => { setError(e); setLoading(false); });
- }, [mock, scope]);
+ }, [scope]);
if (loading) return ;
if (error) return { setError(null); setLoading(true); }} />;
diff --git a/docs/design/pages/screen_users.jsx b/docs/design/pages/screen_users.jsx
index 587bab2..acb18cb 100644
--- a/docs/design/pages/screen_users.jsx
+++ b/docs/design/pages/screen_users.jsx
@@ -58,25 +58,22 @@ function InviteModal({ roles, onClose, onInvited, addToast }) {
}
function ScreenUsers({ addToast }) {
- const mock = !!window.USE_MOCK_DATA;
- const [users, setUsers] = useUsersState(mock ? (CT.adminUsers ?? []) : null);
- const [roles, setRoles] = useUsersState(mock ? (CT.roles ?? []) : []);
- const [loading, setLoading] = useUsersState(!mock);
+ const [users, setUsers] = useUsersState(null);
+ const [roles, setRoles] = useUsersState([]);
+ const [loading, setLoading] = useUsersState(true);
const [error, setError] = useUsersState(null);
const [inviting, setInviting] = useUsersState(false);
const load = useUsersCallback(() => {
- if (mock) return;
setLoading(true);
Promise.all([CT_API.getUsers(), CT_API.getRoles()])
.then(([u, r]) => { setUsers(u); setRoles(r); setLoading(false); setError(null); })
.catch(e => { setError(e); setLoading(false); });
- }, [mock]);
+ }, []);
useUsersEffect(() => { load(); }, [load]);
const act = async (fn, okMsg) => {
- if (mock) { addToast?.('Modo demo: ação não aplicada.', 'info'); return; }
try { await fn(); addToast?.(okMsg, 'check'); load(); }
catch (e) { addToast?.(e?.message ?? 'Falha na ação.', 'alert'); }
};