Files
madbase/tests/integration/functions.test.ts
Vlad Durnea a66d908eff
Some checks failed
CI / podman-build (push) Has been cancelled
CI / rust (push) Has been cancelled
chore: full stack stability and migration fixes, plus react UI progress
2026-03-18 09:01:38 +02:00

428 lines
15 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { createMockedFunction } from './test-utils';
import dotenv from 'dotenv';
import path from 'path';
dotenv.config({ path: path.resolve(process.cwd(), '../../.env') });
describe('Edge Functions', () => {
const functionName = `hello-world-${Date.now()}`;
// Simple WASI module that prints "Hello from WASM!" to stdout
const wat = `
(module
(import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
(memory 1)
(export "memory" (memory 0))
(data (i32.const 8) "Hello from WASM!")
(func $main (export "_start")
(i32.store (i32.const 0) (i32.const 8)) ;; iov.iov_base
(i32.store (i32.const 4) (i32.const 16)) ;; iov.iov_len
(call $fd_write
(i32.const 1) ;; stdout
(i32.const 0) ;; iovs ptr
(i32.const 1) ;; iovs len
(i32.const 20) ;; nwritten ptr
)
drop
)
)
`;
it('should deploy a function', async () => {
const res = await fetch(`${process.env.MADBASE_URL}/functions/v1`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({
name: functionName,
code_base64: Buffer.from(wat).toString('base64')
})
});
if (res.status !== 200) {
console.error('Deploy failed:', await res.text());
}
expect(res.status).toBe(200);
});
it('should invoke a function', async () => {
const res = await fetch(`${process.env.MADBASE_URL}/functions/v1/${functionName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({ payload: { name: 'World' } })
});
if (res.status !== 200) {
console.error('Invoke failed:', await res.text());
}
expect(res.status).toBe(200);
const data = await res.json();
console.log('Invoke response:', data);
expect(data.result).toContain('Hello from WASM!');
});
it('should deploy and invoke a Deno function', async () => {
const name = `deno-hello-${Date.now()}`;
// Simple Deno function that uses Deno.serve shim
const code = `
Deno.serve(async (req) => {
const body = await req.json();
return new Response("Hello " + body.name + " from Deno!");
});
`;
// Deploy
const deployRes = await fetch(`${process.env.MADBASE_URL}/functions/v1`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({
name,
code_base64: Buffer.from(code).toString('base64'),
runtime: 'deno'
})
});
if (deployRes.status !== 200) {
console.error('Deno Deploy failed:', await deployRes.text());
}
expect(deployRes.status).toBe(200);
// Invoke
const invokeRes = await fetch(`${process.env.MADBASE_URL}/functions/v1/${name}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({ payload: { name: 'World' } })
});
if (invokeRes.status !== 200) {
console.error('Deno Invoke failed:', await invokeRes.text());
}
expect(invokeRes.status).toBe(200);
const data = await invokeRes.json();
console.log('Deno Invoke response:', data);
expect(data.result).toBe('Hello World from Deno!');
});
describe('Unit Tests (Component Logic)', () => {
it('should handle missing environment variables', async () => {
const name = `env-check-${Date.now()}`;
const code = createMockedFunction(`
Deno.serve(async (req) => {
const key = Deno.env.get("MY_SECRET_KEY");
if (!key) {
return new Response("Missing Key", { status: 500 });
}
return new Response("Found Key: " + key);
});
`, { env: {} }); // Empty env
// Deploy
await fetch(`${process.env.MADBASE_URL}/functions/v1`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({
name,
code_base64: Buffer.from(code).toString('base64'),
runtime: 'deno'
})
});
// Invoke
const res = await fetch(`${process.env.MADBASE_URL}/functions/v1/${name}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({ payload: {} })
});
const data = await res.json();
expect(data.result).toBe("Missing Key");
expect(data.status).toBe(500);
});
it('should validate request body', async () => {
const name = `body-check-${Date.now()}`;
const code = createMockedFunction(`
Deno.serve(async (req) => {
const body = await req.json();
if (!body.requiredField) {
return new Response("Missing Field", { status: 400 });
}
return new Response("OK");
});
`);
// Deploy
await fetch(`${process.env.MADBASE_URL}/functions/v1`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({
name,
code_base64: Buffer.from(code).toString('base64'),
runtime: 'deno'
})
});
// Invoke (Missing Field)
const res = await fetch(`${process.env.MADBASE_URL}/functions/v1/${name}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({ payload: {} })
});
const data = await res.json();
expect(data.result).toBe("Missing Field");
expect(data.status).toBe(400);
});
});
describe('Integration Tests (System Interactions)', () => {
it('should handle CORS preflight requests', async () => {
const name = `cors-check-${Date.now()}`;
const code = createMockedFunction(`
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
};
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
return new Response("ok", { headers: corsHeaders });
});
`);
await fetch(`${process.env.MADBASE_URL}/functions/v1`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({
name,
code_base64: Buffer.from(code).toString('base64'),
runtime: 'deno'
})
});
// Invoke with OPTIONS (Note: The Gateway might handle this or pass it through.
// Our Deno runtime shim creates a request with POST method by default for invocations,
// so testing OPTIONS strictly via invocation endpoint might need support in the handler/shim.
// For now, we test that the function *can* set headers in response.)
const res = await fetch(`${process.env.MADBASE_URL}/functions/v1/${name}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({ payload: {} })
});
const data = await res.json();
// Check if headers are returned (requires handler update to return headers, which we did)
expect(data.headers['access-control-allow-origin']).toBe('*');
});
});
describe('E2E Workflows (User Flows)', () => {
it('should execute invite staff workflow', async () => {
const name = `invite-staff-${Date.now()}`;
const code = createMockedFunction(`
Deno.serve(async (req) => {
const { email } = await req.json();
// 1. Insert into DB (mocked)
const supabase = createClient();
const { error } = await supabase.from('invitations').insert({ email });
if (error) return new Response("DB Error", { status: 500 });
// 2. Send Email (mocked fetch)
const res = await fetch("https://api.resend.com/emails", {
method: "POST",
body: JSON.stringify({ to: email })
});
if (!res.ok) return new Response("Email Error", { status: 502 });
return new Response("Invite Sent");
});
`, {
fetch: [{ urlPattern: "api.resend.com", status: 200, response: { id: "email_123" } }],
supabase: { insertResult: { id: "invite_123" } }
});
await fetch(`${process.env.MADBASE_URL}/functions/v1`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({
name,
code_base64: Buffer.from(code).toString('base64'),
runtime: 'deno'
})
});
const res = await fetch(`${process.env.MADBASE_URL}/functions/v1/${name}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({ payload: { email: "newuser@example.com" } })
});
const data = await res.json();
expect(data.result).toBe("Invite Sent");
expect(data.status).toBe(200);
});
});
it('should deploy and invoke a complex Polar Checkout-like function', async () => {
const name = `polar-checkout-${Date.now()}`;
const code = createMockedFunction(`
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
try {
const POLAR_API_KEY = Deno.env.get("POLAR_API_KEY");
if (!POLAR_API_KEY) throw new Error("POLAR_API_KEY is not configured");
// Authenticate user
const authHeader = req.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return new Response(JSON.stringify({ error: "Unauthorized: Missing or invalid token" }), {
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const supabase = createClient(
Deno.env.get("SUPABASE_URL"),
Deno.env.get("SUPABASE_ANON_KEY"),
{ global: { headers: { Authorization: authHeader } } }
);
const token = authHeader.replace("Bearer ", "");
const { data: claimsData, error: claimsError } = await supabase.auth.getClaims(token);
if (claimsError || !claimsData?.claims) {
return new Response(JSON.stringify({ error: "Unauthorized: Invalid claims" }), { status: 401 });
}
const { productId, successUrl } = await req.json();
// Create Polar checkout session
const polarRes = await fetch("https://sandbox-api.polar.sh/v1/checkouts/", {
method: "POST",
headers: {
Authorization: "Bearer " + POLAR_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
products: [productId],
success_url: successUrl,
metadata: { user_id: claimsData.claims.sub }
}),
});
const polarData = await polarRes.json();
if (!polarRes.ok) {
throw new Error("Polar API error");
}
return new Response(
JSON.stringify({ url: polarData.url, id: polarData.id }),
{ status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error) {
return new Response(JSON.stringify({ error: String(error) }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
});
`, {
env: {
"POLAR_API_KEY": "mock_polar_key",
"SUPABASE_URL": "http://mock-supabase",
"SUPABASE_ANON_KEY": "mock_anon_key",
"SUPABASE_SERVICE_ROLE_KEY": "mock_service_key"
},
supabase: {
claims: { sub: "user_123", email: "test@example.com" }
},
fetch: [{
urlPattern: "sandbox-api.polar.sh/v1/checkouts/",
status: 200,
response: { url: "https://sandbox.polar.sh/checkout/123", id: "checkout_123" }
}]
});
// Deploy
const deployRes = await fetch(`${process.env.MADBASE_URL}/functions/v1`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({
name,
code_base64: Buffer.from(code).toString('base64'),
runtime: 'deno'
})
});
expect(deployRes.status).toBe(200);
// Invoke
const invokeRes = await fetch(`${process.env.MADBASE_URL}/functions/v1/${name}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
},
body: JSON.stringify({ payload: { productId: "prod_123", successUrl: "http://example.com" } })
});
expect(invokeRes.status).toBe(200);
const data = await invokeRes.json();
console.log('Polar Invoke response:', data);
const result = JSON.parse(data.result);
expect(result.url).toBe("https://sandbox.polar.sh/checkout/123");
});
});