Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ regexes = [
'''sb_publishable_tjH5qAbZ0e5HTTd872NijQ_s9m6JvYU''',
# URL do projeto Supabase (pública — aparece em todas as chamadas de rede)
'''doufsxqlfjyuvxuezpln\.supabase\.co''',
# JWT anon key (role: anon) que estava hardcoded em client.ts antes do commit a9a667ff.
# Substituído por VITE_SUPABASE_PUBLISHABLE_KEY em 2026-05-23.
# Chave anon é pública por design (role sem privilégios, embutida nos bundles).
'''eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRvdWZzeHFsZmp5dXZ4dWV6cGxuIiwicm9sZSI6ImFub24i''',
]

Large diffs are not rendered by default.

14 changes: 12 additions & 2 deletions src/components/search/GlobalSearchIdleState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
} from 'lucide-react';
import { motion } from 'framer-motion';
import { cn } from '@/lib/utils';
import type { QuickAction } from './GlobalSearchHelpers';

/* ── Shared ── */
const paletteItemStateClass =
Expand Down Expand Up @@ -97,6 +96,17 @@ function RankBadge({ index }: { index: number }) {
);
}

/* ── Quick Action type ── */
interface QuickAction {
id: string;
title: string;
description: string;
icon: React.ReactNode;
href: string;
shortcut?: string;
highlight?: boolean;
}

/* ── NavCard ── */
function NavCard({
action,
Expand Down Expand Up @@ -166,7 +176,7 @@ interface GlobalSearchIdleStateProps {
contextualSuggestions: Array<{ id: string; text: string; icon: string; type: string }>;
quickSuggestions: Array<{ label: string; icon: string }>;
routeContext: { section: string };
quickActionsData: QuickAction[];
quickActionsData: Array<QuickAction>;
onSuggestionClick: (text: string) => void;
onSelect: (href: string, addToHistory?: boolean) => void;
onRemoveFromHistory: (e: React.MouseEvent, term: string) => void;
Expand Down
3 changes: 1 addition & 2 deletions src/lib/sentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,11 @@ function shouldLoadSentry(): boolean {
if (!dsn) return false;
if (!isValidSentryDsn(dsn)) {
if (import.meta.env.DEV) {
// eslint-disable-next-line no-console
console.warn(
'[sentry] VITE_SENTRY_DSN tem formato inválido — Sentry não será inicializado. ' +
'Formato esperado: https://<public_key>@<host>/<project_id> ' +
'(public_key sem hífens — UUIDs do GlitchTip não são compatíveis com SDK Sentry 8.x). ' +
'Regenere a Client Key no GlitchTip para obter uma chave alfanumérica compatível.'
'Regenere a Client Key no GlitchTip para obter uma chave alfanumérica compatível.',
);
}
return false;
Expand Down
55 changes: 55 additions & 0 deletions src/tests/AdminStandardRules.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,61 @@ vi.mock('@/components/layout/MainLayout', async (importOriginal) => {
};
});

// Prevent StorageTestPage and other admin pages from making real Supabase
// network calls during render — these cause unhandled rejections in jsdom
// CI with no live server. Uses a Proxy-based chainable mock so every
// .select().eq().maybeSingle() (and any other builder combo) resolves safely.
vi.mock('@/integrations/supabase/client', () => {
const resolved = { data: null, error: null };

function makeChainable(): object {
const p = Promise.resolve(resolved);
const handler: ProxyHandler<object> = {
get(_t, prop) {
if (prop === 'then') return p.then.bind(p);
if (prop === 'catch') return p.catch.bind(p);
if (prop === 'finally') return p.finally.bind(p);
return () => makeChainable();
},
};
return new Proxy({}, handler);
}

const channelMock = {
on: vi.fn().mockReturnThis(),
subscribe: vi.fn().mockReturnThis(),
unsubscribe: vi.fn().mockResolvedValue(undefined),
};

return {
supabase: {
from: () => makeChainable(),
rpc: vi.fn().mockResolvedValue(resolved),
storage: {
from: () => ({
list: vi.fn().mockResolvedValue({ data: [], error: null }),
upload: vi.fn().mockResolvedValue({ data: null, error: null }),
download: vi.fn().mockResolvedValue({ data: null, error: null }),
remove: vi.fn().mockResolvedValue({ data: null, error: null }),
getPublicUrl: vi.fn().mockReturnValue({ data: { publicUrl: '' } }),
}),
},
functions: { invoke: vi.fn().mockResolvedValue({ data: null, error: null }) },
channel: vi.fn().mockReturnValue(channelMock),
removeChannel: vi.fn().mockResolvedValue(undefined),
auth: {
getUser: vi.fn().mockResolvedValue({ data: { user: null }, error: null }),
getSession: vi.fn().mockResolvedValue({ data: { session: null }, error: null }),
onAuthStateChange: vi.fn().mockReturnValue({
data: { subscription: { unsubscribe: vi.fn() } },
}),
signInWithPassword: vi.fn().mockResolvedValue({ data: null, error: null }),
signOut: vi.fn().mockResolvedValue({ error: null }),
},
},
};
});

// Capture PageSEO props
const seoCaptures: Record<string, Record<string, unknown>> = {};
vi.mock('@/components/seo/PageSEO', () => ({
Expand Down
29 changes: 22 additions & 7 deletions src/tests/AdminStructuralComparison.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ const wrapper = ({ children }: { children: React.ReactNode }) => (
<ThemeProvider>
<AuthProvider>
<TooltipProvider>
<AriaLiveProvider>
{children}
</AriaLiveProvider>
<AriaLiveProvider>{children}</AriaLiveProvider>
</TooltipProvider>
</AuthProvider>
</ThemeProvider>
Expand All @@ -31,7 +29,6 @@ const wrapper = ({ children }: { children: React.ReactNode }) => (
</QueryClientProvider>
);


// Mock hooks that use network/Supabase to avoid async leaks during tests
vi.mock('@/hooks/admin', () => ({
useSecretsManager: () => ({
Expand Down Expand Up @@ -83,14 +80,32 @@ vi.mock('@/components/admin/DevAccessAuditAlert', () => ({
DevAccessAuditAlert: () => <div data-testid="dev-audit-alert">Dev Audit</div>,
}));

// AdminConexoesPage renders SupabaseConnectionsTab which has useEffect async
// calls (fetchLastTest) that fire setLastByEnv after the test environment is
// torn down, causing "window is not defined". Mock the tab component itself to
// remove all async operations while preserving the page's container structure.
vi.mock('@/components/admin/connections/SupabaseConnectionsTab', () => ({
SupabaseConnectionsTab: () => <div data-testid="supabase-connections-tab-mock" />,
}));

// Mock ConnectionsOverviewTable and SecretField which also transitively use
// @/hooks/intelligence and trigger similar post-teardown async errors.
vi.mock('@/components/admin/connections/ConnectionsOverviewTable', () => ({
ConnectionsOverviewTable: () => <div data-testid="connections-overview-mock" />,
}));

vi.mock('@/components/admin/connections/SecretField', () => ({
SecretField: () => <div data-testid="secret-field-mock" />,
}));

describe('Admin Module Structural Comparison', () => {
it('Conexoes and Usuarios should share matching container hierarchy', async () => {
const { container: conexoes } = render(<AdminConexoesPage />, { wrapper });
const { container: usuarios } = render(<AdminUsuariosPage />, { wrapper });

// Select the standardized inner container (div with max-w inside main)
const findContainer = (root: HTMLElement) =>
Array.from(root.querySelectorAll('div')).find(d => d.className.includes('max-w-'));
const findContainer = (root: HTMLElement) =>
Array.from(root.querySelectorAll('div')).find((d) => d.className.includes('max-w-'));

const conexoesInner = findContainer(conexoes);
const usuariosInner = findContainer(usuarios);
Expand Down
3 changes: 1 addition & 2 deletions supabase/functions/comparison-ai-advisor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { authenticateRequest, requireRole, authErrorResponse } from '../_shared/
// Comparison AI Advisor — Lovable AI Gateway
// Recebe lista slim de produtos e retorna 3-5 bullets + bestFor highVolume/fastDelivery/premium.

import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { z } from 'https://deno.land/x/zod@v3.22.4/mod.ts';
import { safeErrorFields } from '../_shared/log-safety.ts';

Expand Down Expand Up @@ -65,7 +64,7 @@ const ToolSchema = {
},
};

serve(async (req) => {
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') return new Response('ok', { headers: getCorsHeaders(req) });

corsHeaders = getCorsHeaders(req);
Expand Down
13 changes: 11 additions & 2 deletions supabase/functions/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@
"//behavior": "Deno a usar seu cache local em vez de exigir node_modules.",
"nodeModulesDir": "none",
"lock": false,
"//imports": "Import map: redireciona esm.sh → npm: para evitar falhas de TLS/rede no CI.",
"//imports": "Import map: redireciona esm.sh → npm: e deno.land → npm:/jsr: para evitar falhas de TLS/rede no CI.",
"imports": {
"https://esm.sh/zod@3.23.8": "npm:zod@3.23.8",
"https://esm.sh/@supabase/supabase-js@2": "npm:@supabase/supabase-js@2",
"https://esm.sh/@supabase/supabase-js@2.45.0": "npm:@supabase/supabase-js@2.45.0",
"https://esm.sh/@supabase/supabase-js@2.49.1": "npm:@supabase/supabase-js@2.49.1",
"https://esm.sh/@supabase/supabase-js@2.49.4": "npm:@supabase/supabase-js@2.49.4",
"https://esm.sh/@supabase/supabase-js@2.95.0": "npm:@supabase/supabase-js@2.95.0"
"https://esm.sh/@supabase/supabase-js@2.95.0": "npm:@supabase/supabase-js@2.95.0",
"https://deno.land/x/zod@v3.23.8/mod.ts": "npm:zod@3.23.8",
"https://deno.land/x/zod@v3.22.4/mod.ts": "npm:zod@3.22.4",
"https://deno.land/std@0.224.0/crypto/mod.ts": "jsr:@std/crypto@^0.224.0",
"https://deno.land/std@0.224.0/encoding/hex.ts": "jsr:@std/encoding@^0.224.0/hex",
"https://deno.land/std@0.224.0/assert/mod.ts": "jsr:@std/assert@^0.224.0",
"https://deno.land/std@0.208.0/assert/mod.ts": "jsr:@std/assert@^0.208.0",
"https://deno.land/std@0.224.0/dotenv/load.ts": "jsr:@std/dotenv@^0.224.0/load",
"https://deno.land/std@0.224.0/testing/bdd.ts": "jsr:@std/testing@^0.224.0/bdd"
},
"lint": {
"rules": {
Expand Down
9 changes: 8 additions & 1 deletion supabase/functions/mcp-server/deno.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
{
"nodeModulesDir": "none",
"lock": false,
"imports": {
"hono": "npm:hono@4.6.14",
"mcp-lite": "npm:mcp-lite@^0.10.0",
"@supabase/supabase-js": "npm:@supabase/supabase-js@2.49.4"
"@supabase/supabase-js": "npm:@supabase/supabase-js@2.49.4",
"https://esm.sh/zod@3.23.8": "npm:zod@3.23.8",
"https://esm.sh/@supabase/supabase-js@2.45.0": "npm:@supabase/supabase-js@2.45.0",
"https://esm.sh/@supabase/supabase-js@2.49.1": "npm:@supabase/supabase-js@2.49.1",
"https://esm.sh/@supabase/supabase-js@2.49.4": "npm:@supabase/supabase-js@2.49.4",
"https://esm.sh/@supabase/supabase-js@2.95.0": "npm:@supabase/supabase-js@2.95.0"
}
}
6 changes: 3 additions & 3 deletions supabase/functions/mcp-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { buildPublicCorsHeaders, getCorsHeaders } from "../_shared/cors.ts";
// Authenticates via X-MCP-Key header (validated in DB against mcp_api_keys.key_hash).
// Each tool declares { scope, mode } and is gated centrally before running.
// Every tool invocation is audited (granted, denied, error) with consistent error codes.
import { Hono } from "hono";
import { type Context, Hono } from "hono";
import { McpServer, StreamableHttpTransport } from "mcp-lite";
import { createClient } from "@supabase/supabase-js";
import { getOrCreateRequestId, REQUEST_ID_HEADER } from "../_shared/request-id.ts";
Expand Down Expand Up @@ -369,11 +369,11 @@ mcpServer.tool("ping", {
const transport = new StreamableHttpTransport();
const app = new Hono();

app.options("/*", (c) => new Response(null, { headers: getCorsHeaders(c.req.raw) }));
app.options("/*", (c: Context) => new Response(null, { headers: getCorsHeaders(c.req.raw) }));

const httpHandler = transport.bind(mcpServer);

app.all("/*", async (c) => {
app.all("/*", async (c: Context) => {
const auth = await authenticate(c.req.raw);
const reqId = getOrCreateRequestId(c.req.raw);
const ip =
Expand Down
3 changes: 1 addition & 2 deletions supabase/functions/send-transactional-email/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { getCorsHeaders } from "../_shared/cors.ts";
* Envia emails transacionais para eventos do sistema.
* Suporta: quote_sent, quote_approved, quote_rejected, order_created.
*/
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.4";
import { parseContract } from "../_shared/contracts/index.ts";
import {
Expand Down Expand Up @@ -129,7 +128,7 @@ function buildEmailContent(event: EmailRequest): { subject: string; html: string
}
}

serve(async (req) => {
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: getCorsHeaders(req) });
}
Expand Down
3 changes: 1 addition & 2 deletions supabase/functions/simulation-orchestrator/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createStructuredLogger } from "../_shared/structured-logger.ts";
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.4";
import { encodeHex } from "https://deno.land/std@0.224.0/encoding/hex.ts";
import { parseContract } from "../_shared/contracts/index.ts";
Expand Down Expand Up @@ -43,7 +42,7 @@ function generateFuzzedPayload(type: string) {
return { [String(item)]: item };
}

serve(async (req) => {
Deno.serve(async (req) => {
if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders });

const startTime = performance.now();
Expand Down
3 changes: 1 addition & 2 deletions supabase/functions/sync-external-db/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createStructuredLogger } from "../_shared/structured-logger.ts";
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
import { parseContract } from "../_shared/contracts/index.ts";
import {
Expand All @@ -9,7 +8,7 @@ import { buildPublicCorsHeaders } from "../_shared/cors.ts";

const corsHeaders = buildPublicCorsHeaders();

serve(async (req) => {
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
Expand Down
Loading