Update dependencies in deno.lock and add tests for ai-structured function
This commit is contained in:
@@ -2,6 +2,9 @@
|
|||||||
"version": "5",
|
"version": "5",
|
||||||
"specifiers": {
|
"specifiers": {
|
||||||
"jsr:@openai/openai@*": "6.16.0",
|
"jsr:@openai/openai@*": "6.16.0",
|
||||||
|
"jsr:@std/assert@1": "1.0.16",
|
||||||
|
"jsr:@std/dotenv@*": "0.225.6",
|
||||||
|
"jsr:@std/internal@^1.0.12": "1.0.12",
|
||||||
"jsr:@supabase/functions-js@*": "2.90.1",
|
"jsr:@supabase/functions-js@*": "2.90.1",
|
||||||
"npm:@supabase/supabase-js@^2.90.1": "2.90.1",
|
"npm:@supabase/supabase-js@^2.90.1": "2.90.1",
|
||||||
"npm:@types/bun@^1.3.5": "1.3.5",
|
"npm:@types/bun@^1.3.5": "1.3.5",
|
||||||
@@ -13,6 +16,18 @@
|
|||||||
"@openai/openai@6.16.0": {
|
"@openai/openai@6.16.0": {
|
||||||
"integrity": "ccee548f61c382d715091fff0c2c3390a4487823644430b8235df543d6d6a78b"
|
"integrity": "ccee548f61c382d715091fff0c2c3390a4487823644430b8235df543d6d6a78b"
|
||||||
},
|
},
|
||||||
|
"@std/assert@1.0.16": {
|
||||||
|
"integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532",
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/internal"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@std/dotenv@0.225.6": {
|
||||||
|
"integrity": "1d6f9db72f565bd26790fa034c26e45ecb260b5245417be76c2279e5734c421b"
|
||||||
|
},
|
||||||
|
"@std/internal@1.0.12": {
|
||||||
|
"integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027"
|
||||||
|
},
|
||||||
"@supabase/functions-js@2.90.1": {
|
"@supabase/functions-js@2.90.1": {
|
||||||
"integrity": "9a077ecf42aa84594ef3aac621d643e15ffaa9506a4c051e35c93f127ffdb035"
|
"integrity": "9a077ecf42aa84594ef3aac621d643e15ffaa9506a4c051e35c93f127ffdb035"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
// 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 { data, error } = await client.functions.invoke("ai-structured", {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`, // 👈 clave para que tu función pase el authHeader check
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
response: {
|
||||||
|
model: "gpt-5",
|
||||||
|
input: [
|
||||||
|
{ role: "system", content: "Responde SIEMPRE en JSON válido." },
|
||||||
|
{ role: "user", content: "Dame 3 ideas de proyecto de IA para educación." },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
structured: {
|
||||||
|
type: "json_schema",
|
||||||
|
name: "ideas",
|
||||||
|
strict: true,
|
||||||
|
schema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
ideas: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
titulo: { type: "string" },
|
||||||
|
descripcion: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["titulo", "descripcion"],
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["ideas"],
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
references: {},
|
||||||
|
usarMCP: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) throw new Error("Invoke failed: " + error.message);
|
||||||
|
assert(data, "Expected data from function");
|
||||||
|
|
||||||
|
// tu función responde { ok, output, outputText, ... }
|
||||||
|
assertEquals(data.ok, true);
|
||||||
|
assert(Array.isArray(data.output?.ideas), "output.ideas should be an array");
|
||||||
|
assertEquals(data.output.ideas.length, 3);
|
||||||
|
for (const it of data.output.ideas) {
|
||||||
|
assert(typeof it.titulo === "string");
|
||||||
|
assert(typeof it.descripcion === "string");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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" },
|
||||||
|
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));
|
||||||
|
});
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { supabase } from "./new-user";
|
|
||||||
|
|
||||||
const file = Bun.file("files/carta.pdf");
|
|
||||||
|
|
||||||
await supabase.auth.signInWithPassword({
|
|
||||||
email: "guillermo.arrieta@lasalle.mx",
|
|
||||||
password: "admin",
|
|
||||||
});
|
|
||||||
|
|
||||||
const fd = new FormData();
|
|
||||||
fd.append("payload", JSON.stringify({
|
|
||||||
response: { input: "Resume estos documentos en JSON.", model: "gpt-5" },
|
|
||||||
structured: { type: "json_schema", name: "resumen", strict: true, schema: {
|
|
||||||
type: "object", properties: { resumen: { type: "string" } }, required: ["resumen"], additionalProperties: false
|
|
||||||
} },
|
|
||||||
storage: { prefix: "tmp" },
|
|
||||||
}));
|
|
||||||
fd.append("files", file);
|
|
||||||
|
|
||||||
const response = await supabase.functions.invoke("ai-structured", {
|
|
||||||
method: "POST",
|
|
||||||
body: fd,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log({ d: response.data, e: response.error });
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { supabase } from "./new-user";
|
|
||||||
|
|
||||||
const /* { data, error } */_ = await supabase.auth.signInWithPassword({
|
|
||||||
email: "guillermo.arrieta@lasalle.mx",
|
|
||||||
password: "admin",
|
|
||||||
});
|
|
||||||
|
|
||||||
/* if (error) {
|
|
||||||
console.error("Error signing in:", error);
|
|
||||||
process.exit(1);
|
|
||||||
} else {
|
|
||||||
console.log("Successfully signed in:", data);
|
|
||||||
} */
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
const response = await supabase.functions.invoke("ai-structured", {
|
|
||||||
body: {
|
|
||||||
response: {
|
|
||||||
model: "gpt-5",
|
|
||||||
input: [
|
|
||||||
{ role: "system", content: "Responde SIEMPRE en JSON válido." },
|
|
||||||
{ role: "user", content: "Dame 3 ideas de proyecto de IA para educación." },
|
|
||||||
],
|
|
||||||
/* conversation: "conv_...", */ // opcional
|
|
||||||
},
|
|
||||||
structured: {
|
|
||||||
type: "json_schema",
|
|
||||||
name: "ideas",
|
|
||||||
strict: true,
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
ideas: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
titulo: { type: "string" },
|
|
||||||
descripcion: { type: "string" }
|
|
||||||
},
|
|
||||||
required: ["titulo", "descripcion"],
|
|
||||||
additionalProperties: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["ideas"],
|
|
||||||
additionalProperties: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
references: {
|
|
||||||
/* vectorStoreIds: ["vs_..."], */ // opcional (file_search) ✅
|
|
||||||
/* openaiFileIds: ["file_..."], */ // opcional (input_file) ✅
|
|
||||||
},
|
|
||||||
usarMCP: false, // opcional ✅
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log({ d: response.data, e: response.error });
|
|
||||||
} catch (e: any) {
|
|
||||||
const res = e?.context ?? e?.response;
|
|
||||||
console.log("status", res?.status);
|
|
||||||
if (res?.text) console.log("text", await res.text());
|
|
||||||
else console.log("raw error keys", Object.keys(e ?? {}));
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { createClient } from '@supabase/supabase-js'
|
|
||||||
|
|
||||||
export const supabase = createClient(Bun.env.SUPABASE_URL ?? '', Bun.env.SUPABASE_ANON_KEY ?? '')
|
|
||||||
/* try {
|
|
||||||
const { data, error } = await supabase.auth.signUp({
|
|
||||||
email: 'guillermo.arrieta@lasalle.mx',
|
|
||||||
password: 'sadfasdfsadfdsasadfsad',
|
|
||||||
})
|
|
||||||
console.log({ data, error })
|
|
||||||
} catch (error) {
|
|
||||||
console.log({ error })
|
|
||||||
} */
|
|
||||||
Reference in New Issue
Block a user