feat: initialize database schema with essential tables, types, and functions for academic management system
- Created multiple tables including 'asignaturas', 'carreras', 'planes_estudio', and others to manage academic entities. - Defined various ENUM types for categorizing academic roles, task statuses, and notification types. - Implemented triggers and functions for automatic timestamp updates and validation of cycle numbers in subjects. - Established foreign key relationships to maintain data integrity across tables. - Added necessary extensions for enhanced functionality, including 'pgsodium', 'pg_graphql', and 'pg_stat_statements'.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
// 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-generate-plan (JSON body)", async () => {
|
||||
const { client, accessToken } = await getAuthedClient();
|
||||
|
||||
// Get any carrera
|
||||
const { data: carrera, error: carreraError } = await client
|
||||
.from("carreras")
|
||||
.select("id").limit(1).single();
|
||||
if (carreraError) {
|
||||
throw new Error("Failed to fetch carrera: " + carreraError.message);
|
||||
}
|
||||
if (!carrera) throw new Error("No carrera found");
|
||||
// datosBasicos (nombrePlan,carreraId,nivel,tipoCiclo,numCiclos)
|
||||
const datosBasicos = {
|
||||
nombrePlan: "Plan de estudios de Ingeniería en Sistemas Computacionales",
|
||||
carreraId: carrera.id,
|
||||
nivel: "Licenciatura",
|
||||
tipoCiclo: "Semestral",
|
||||
numCiclos: 8,
|
||||
};
|
||||
|
||||
const { data, error } = await client.functions.invoke("ai_generate_plan", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`, // 👈 clave para que tu función pase el authHeader check
|
||||
},
|
||||
body: {
|
||||
datosBasicos,
|
||||
},
|
||||
});
|
||||
|
||||
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(data.plan);
|
||||
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));
|
||||
});
|
||||
*/
|
||||
Reference in New Issue
Block a user