c51281856a
- Added imports for OpenAI and Zod in deno.json - Updated deno.lock with new versions for OpenAI and Zod - Introduced a sample PDF file demonstrating bookmark functionality - Refactored ai-structured function to improve error handling and logging - Updated tests to reflect changes in input structure and expected output
141 lines
4.1 KiB
TypeScript
141 lines
4.1 KiB
TypeScript
// supabase/functions/ai-structured/ai_structured.test.ts
|
|
/// <reference lib="deno.ns" />
|
|
/// <reference lib="dom" />
|
|
/// <reference lib="dom.iterable" />
|
|
|
|
import { assert, assertEquals } from "jsr:@std/assert@1";
|
|
import "jsr:@std/dotenv/load";
|
|
import { createClient, type SupabaseClient } from "npm:@supabase/supabase-js@2";
|
|
|
|
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? "";
|
|
const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY") ?? ""; // (o PUBLISHABLE_KEY si así lo nombraste)
|
|
const EMAIL = Deno.env.get("TEST_EMAIL") ?? "guillermo.arrieta@lasalle.mx";
|
|
const PASSWORD = Deno.env.get("TEST_PASSWORD") ?? "admin";
|
|
|
|
const options = {
|
|
auth: {
|
|
autoRefreshToken: false,
|
|
persistSession: false,
|
|
detectSessionInUrl: false,
|
|
},
|
|
};
|
|
|
|
function mustEnv() {
|
|
if (!SUPABASE_URL) throw new Error("SUPABASE_URL is required");
|
|
if (!SUPABASE_ANON_KEY) throw new Error("SUPABASE_ANON_KEY is required");
|
|
}
|
|
|
|
async function getAuthedClient(): Promise<
|
|
{ client: SupabaseClient; accessToken: string }
|
|
> {
|
|
mustEnv();
|
|
const client = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, options);
|
|
|
|
const { data, error } = await client.auth.signInWithPassword({
|
|
email: EMAIL,
|
|
password: PASSWORD,
|
|
});
|
|
if (error) throw new Error("Sign-in failed: " + error.message);
|
|
|
|
const accessToken = data.session?.access_token;
|
|
if (!accessToken) {
|
|
throw new Error("No access_token returned from signInWithPassword");
|
|
}
|
|
|
|
return { client, accessToken };
|
|
}
|
|
|
|
Deno.test("ai-structured (JSON body)", async () => {
|
|
const { client, accessToken } = await getAuthedClient();
|
|
|
|
const form = new FormData();
|
|
form.append(
|
|
"options",
|
|
JSON.stringify({
|
|
input: "Genera 3 ideas de negocio innovadoras en formato JSON.",
|
|
model: "gpt-5-nano",
|
|
text: {
|
|
format: {
|
|
"type": "json_schema",
|
|
"name": "business_ideas",
|
|
"schema": {
|
|
"type": "array",
|
|
"properties": {
|
|
"idea": { "type": "string", "description": "The business idea" },
|
|
},
|
|
"required": ["idea"],
|
|
"additionalProperties": false,
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
|
|
const { data, error } = await client.functions.invoke("ai-structured", {
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`, // 👈 clave para que tu función pase el authHeader check
|
|
},
|
|
body: form,
|
|
});
|
|
|
|
if (error) throw new Error("Invoke failed: " + error.message);
|
|
assert(data, "Expected data from function");
|
|
assertEquals(data.ok, true);
|
|
assert(
|
|
Array.isArray(data.openai_raw.output),
|
|
"Expected output to be an array",
|
|
);
|
|
assert(data.openai_raw.output.length === 3, "Expected 3 business ideas");
|
|
console.log(data);
|
|
});
|
|
|
|
Deno.test("ai-structured (multipart + file)", async () => {
|
|
const { client, accessToken } = await getAuthedClient();
|
|
|
|
// Lee un PDF local (ajusta path)
|
|
const bytes = await Deno.readFile("files/carta.pdf");
|
|
const file = new File([bytes], "carta.pdf", { type: "application/pdf" });
|
|
|
|
const payload = {
|
|
response: {
|
|
input: "Resume estos documentos en JSON.",
|
|
model: "gpt-5-nano",
|
|
},
|
|
structured: {
|
|
type: "json_schema",
|
|
name: "resumen",
|
|
strict: true,
|
|
schema: {
|
|
type: "object",
|
|
properties: { resumen: { type: "string" } },
|
|
required: ["resumen"],
|
|
additionalProperties: false,
|
|
},
|
|
},
|
|
storage: { prefix: "tmp" },
|
|
};
|
|
|
|
const fd = new FormData();
|
|
fd.append("payload", JSON.stringify(payload));
|
|
fd.append("files", file);
|
|
|
|
const { data, error } = await client.functions.invoke("ai-structured", {
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`, // 👈 necesario
|
|
// NO pongas Content-Type: fetch lo setea con boundary para FormData ✅
|
|
},
|
|
method: "POST",
|
|
body: fd,
|
|
});
|
|
|
|
if (error) throw new Error("Invoke failed: " + error.message);
|
|
assert(data, "Expected data from function");
|
|
|
|
assertEquals(data.ok, true);
|
|
assert(typeof data.output?.resumen === "string");
|
|
assert(data.output.resumen.length > 0);
|
|
|
|
// opcional: valida que registró upload(s) a storage
|
|
assert(Array.isArray(data.references?.uploadedToStorage));
|
|
});
|