S
Supabase•4mo ago
SB

Edge Functions - inconsistent results when running .invoke locally using supabase serve

I have this simple edge function, im trying to test requests that are not POST. I keep getting mixed responses. Please help 🙏 message: Method not allowed status: 405 headers: POST or message: undefined status: 502 headers: null Edge Function
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

export function createHandler(tasks = originalTasks) {
return async function (req: Request) {
if (req.method !== 'POST') {
console.log(`Method not allowed: ${req.method}`);
return new Response(JSON.stringify({ error: 'Method not allowed' }), {
headers: { ...corsHeaders, 'Allow': 'POST' },
status: 405,
});
}

return new Response(JSON.stringify('hello'), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
});
};
}
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

export function createHandler(tasks = originalTasks) {
return async function (req: Request) {
if (req.method !== 'POST') {
console.log(`Method not allowed: ${req.method}`);
return new Response(JSON.stringify({ error: 'Method not allowed' }), {
headers: { ...corsHeaders, 'Allow': 'POST' },
status: 405,
});
}

return new Response(JSON.stringify('hello'), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
});
};
}
Test
Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const anonClient = createClient<Database>(supabaseUrl, anonKey, options);

// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('update-system-settings-handler', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

// log the error message
const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const anonClient = createClient<Database>(supabaseUrl, anonKey, options);

// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('update-system-settings-handler', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

// log the error message
const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
47 Replies
garyaustin
garyaustin•4mo ago
I don't see you handling the OPTIONS request. When you use .invoke() from a browser you have to handle a return for the OPTIONS request.
SB
SBOP•4mo ago
im using invoke in Deno.test not from a browser?
garyaustin
garyaustin•4mo ago
No idea how that works, but assume it would not do options request. What do you see in the EDGE function logs for invocation in both cases and the console.log of the type of request?
SB
SBOP•4mo ago
on a successful run i get this 2025-07-03T18:06:45.639379846Z Serving functions on http://127.0.0.1:54321/functions/v1/<function-name> 2025-07-03T18:06:45.639394387Z - http://127.0.0.1:54321/functions/v1/contact-us 2025-07-03T18:06:45.639395846Z - http://127.0.0.1:54321/functions/v1/current-usage 2025-07-03T18:06:45.639396554Z - http://127.0.0.1:54321/functions/v1/delete-account 2025-07-03T18:06:45.639397221Z - http://127.0.0.1:54321/functions/v1/update-system-settings-handler 2025-07-03T18:06:45.639397887Z Using supabase-edge-runtime-1.67.4 (compatible with Deno v1.45.2) 2025-07-03T18:06:46.554569096Z serving the request with supabase/functions/update-system-settings-handler 2025-07-03T18:06:46.750338721Z [Info] Request: {} 2025-07-03T18:06:46.750369013Z 2025-07-03T18:06:46.750371221Z [Info] Method not allowed: PUT 2025-07-03T18:06:46.750372221Z on a failed run i get these logs 025-07-03T18:06:51.427318626Z Serving functions on http://127.0.0.1:54321/functions/v1/<function-name> 2025-07-03T18:06:51.427339542Z - http://127.0.0.1:54321/functions/v1/contact-us 2025-07-03T18:06:51.427341042Z - http://127.0.0.1:54321/functions/v1/current-usage 2025-07-03T18:06:51.427341792Z - http://127.0.0.1:54321/functions/v1/delete-account 2025-07-03T18:06:51.427342417Z - http://127.0.0.1:54321/functions/v1/update-system-settings-handler 2025-07-03T18:06:51.427343084Z Using supabase-edge-runtime-1.67.4 (compatible with Deno v1.45.2) There is no consistency between it running successfully or failing. It’s like it is not running the code. When it fails, I don’t get the first line of the function, outputting the request - [Info] Request: {}
garyaustin
garyaustin•4mo ago
Anything useful in this log?
No description
garyaustin
garyaustin•4mo ago
I don't recognize your logs. Mine look nothing like that. But it is an already deployed function.
No description
garyaustin
garyaustin•4mo ago
Are you not running a hosted function?
SB
SBOP•4mo ago
This is when running the edge functions locally.
garyaustin
garyaustin•4mo ago
Add that to your title or first post. I don't do that so not sure what type of things you could run into. Is the deno.test also a locally running thing?
SB
SBOP•4mo ago
ok no problem sure. when running against our dev, i didn’t see any errors.
No description
inder
inder•4mo ago
How do you call this function in Deno.serve?
SB
SBOP•4mo ago
// Pass the handler with the default tasks module to Deno.serve Deno.serve(createHandler()); I completed some more testing this morning, commenting out all other tests, this passes every time as soon as i uncomment like in the example below there is an intermittent failure.
import { assert, assertEquals } from 'jsr:@std/assert';
import { createHandler } from '../update-system-settings-handler/index.ts';
import { Database } from "@/database-types";
import { createClient } from '@supabase/supabase-js'
import { tasks } from '@trigger.dev/sdk';

//#region - Configurations
// Set up the configuration for the Supabase client
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const anonKey = Deno.env.get('SUPABASE_ANON_KEY')!;
const options = {
auth: {
autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false
}
};
//#endregion

//#region - Mock Trigger.dev
const mockTasks = {
trigger: (jobName: string, payload: object) => {
return { id: "mock-id", status: "success", jobName, payload };
},
} as unknown as typeof tasks;
//#endregion
import { assert, assertEquals } from 'jsr:@std/assert';
import { createHandler } from '../update-system-settings-handler/index.ts';
import { Database } from "@/database-types";
import { createClient } from '@supabase/supabase-js'
import { tasks } from '@trigger.dev/sdk';

//#region - Configurations
// Set up the configuration for the Supabase client
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const anonKey = Deno.env.get('SUPABASE_ANON_KEY')!;
const options = {
auth: {
autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false
}
};
//#endregion

//#region - Mock Trigger.dev
const mockTasks = {
trigger: (jobName: string, payload: object) => {
return { id: "mock-id", status: "success", jobName, payload };
},
} as unknown as typeof tasks;
//#endregion
// Test triggering the update system settings handler
Deno.test('Test triggering the update system settings handler', async () => {
// Use the handler with the mocked `tasks` object
const handler = createHandler(mockTasks);

// Simulate the POST request to the handler
const request = new Request('http://localhost', {
method: 'POST',
body: JSON.stringify({ householdId: '1', delayedTimeOfUseUpdateRequired: true }),
});

const response = await handler(request);
const data = await response.json();

// Assertions
assertEquals(response.status, 200);
assertEquals(data, { id: "mock-id", status: "success", jobName: "update-system-settings", payload: { householdId: '1', delayedTimeOfUseUpdateRequired: true } });
});

// Test that the function returns an error if the request method is not a POST
Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const anonClient = createClient<Database>(supabaseUrl, anonKey, options);

// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('update-system-settings-handler', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

// log the error message
const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
// Test triggering the update system settings handler
Deno.test('Test triggering the update system settings handler', async () => {
// Use the handler with the mocked `tasks` object
const handler = createHandler(mockTasks);

// Simulate the POST request to the handler
const request = new Request('http://localhost', {
method: 'POST',
body: JSON.stringify({ householdId: '1', delayedTimeOfUseUpdateRequired: true }),
});

const response = await handler(request);
const data = await response.json();

// Assertions
assertEquals(response.status, 200);
assertEquals(data, { id: "mock-id", status: "success", jobName: "update-system-settings", payload: { householdId: '1', delayedTimeOfUseUpdateRequired: true } });
});

// Test that the function returns an error if the request method is not a POST
Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const anonClient = createClient<Database>(supabaseUrl, anonKey, options);

// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('update-system-settings-handler', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

// log the error message
const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
sorry had to split the example into two part due to character limitation
inder
inder•4mo ago
Ok, test works in deno as well.
import { createClient } from 'npm:@supabase/supabase-js@2'
import { assert } from "jsr:@std/assert";

const options = {
auth: {
autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false
}
};

Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const supabaseUrl = "http://localhost:54321";
const anonKey="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";

const anonClient = createClient(supabaseUrl, anonKey,options);
// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('hello', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
import { createClient } from 'npm:@supabase/supabase-js@2'
import { assert } from "jsr:@std/assert";

const options = {
auth: {
autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false
}
};

Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const supabaseUrl = "http://localhost:54321";
const anonKey="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";

const anonClient = createClient(supabaseUrl, anonKey,options);
// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('hello', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
I've ran the test multiple times, test passes
for i in $(seq 300); do
if ! deno test --allow-env --no-check --allow-net my_test.ts;then
echo "failed"
exit 1
fi
done
for i in $(seq 300); do
if ! deno test --allow-env --no-check --allow-net my_test.ts;then
echo "failed"
exit 1
fi
done
SB
SBOP•4mo ago
Have you tried adding a second test, for example, check that POST is allowed. I found that as soon as there was a second test in the file it started random errors
inder
inder•4mo ago
I didn't run a test for that, but checked via browser. That works. Let me run a test for that too. No issues
import { createClient } from 'npm:@supabase/supabase-js@2'
import { assert } from "jsr:@std/assert";

const options = {
auth: {
autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false
}
};

Deno.test('[Unit Test] Method IS POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const supabaseUrl = "http://localhost:54321";
const anonKey="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";

const anonClient = createClient(supabaseUrl, anonKey,options);
// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('hello', {
method: 'POST',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

if(error){
const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);
}
// Check for errors from the function invocation
if (!data || error) {
assert(false);
}
});
import { createClient } from 'npm:@supabase/supabase-js@2'
import { assert } from "jsr:@std/assert";

const options = {
auth: {
autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false
}
};

Deno.test('[Unit Test] Method IS POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const supabaseUrl = "http://localhost:54321";
const anonKey="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";

const anonClient = createClient(supabaseUrl, anonKey,options);
// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('hello', {
method: 'POST',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

if(error){
const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);
}
// Check for errors from the function invocation
if (!data || error) {
assert(false);
}
});
Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const supabaseUrl = "http://localhost:54321";
const anonKey="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";

const anonClient = createClient(supabaseUrl, anonKey,options);
// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('hello', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const supabaseUrl = "http://localhost:54321";
const anonKey="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";

const anonClient = createClient(supabaseUrl, anonKey,options);
// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('hello', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
Function:
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'

const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

const testFn = () => {
return async function (req: Request) {
if (req.method !== 'POST') {
console.log(`Method not allowed: ${req.method}`)
return new Response(JSON.stringify({ error: 'Method not allowed' }), {
headers: { ...corsHeaders, Allow: 'POST' },
status: 405,
})
}

return new Response(JSON.stringify('hello'), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
})
}
}

Deno.serve(testFn())
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'

const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

const testFn = () => {
return async function (req: Request) {
if (req.method !== 'POST') {
console.log(`Method not allowed: ${req.method}`)
return new Response(JSON.stringify({ error: 'Method not allowed' }), {
headers: { ...corsHeaders, Allow: 'POST' },
status: 405,
})
}

return new Response(JSON.stringify('hello'), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
})
}
}

Deno.serve(testFn())
SB
SBOP•4mo ago
there seems to be some sort of clash between this test
const mockTasks = {
trigger: (jobName: string, payload: object) => {
return { id: "mock-id", status: "success", jobName, payload };
},
} as unknown as typeof tasks;

Deno.test('Test triggering the update system settings handler', async () => {
// Use the handler with the mocked `tasks` object
const handler = createHandler(mockTasks);

// Simulate the POST request to the handler
const request = new Request('http://localhost', {
method: 'POST',
body: JSON.stringify({ householdId: '1', delayedTimeOfUseUpdateRequired: true }),
});

const response = await handler(request);
const data = await response.json();

// Assertions
assertEquals(response.status, 200);
assertEquals(data, { id: "mock-id", status: "success", jobName: "update-system-settings", payload: { householdId: '1', delayedTimeOfUseUpdateRequired: true } });
});
const mockTasks = {
trigger: (jobName: string, payload: object) => {
return { id: "mock-id", status: "success", jobName, payload };
},
} as unknown as typeof tasks;

Deno.test('Test triggering the update system settings handler', async () => {
// Use the handler with the mocked `tasks` object
const handler = createHandler(mockTasks);

// Simulate the POST request to the handler
const request = new Request('http://localhost', {
method: 'POST',
body: JSON.stringify({ householdId: '1', delayedTimeOfUseUpdateRequired: true }),
});

const response = await handler(request);
const data = await response.json();

// Assertions
assertEquals(response.status, 200);
assertEquals(data, { id: "mock-id", status: "success", jobName: "update-system-settings", payload: { householdId: '1', delayedTimeOfUseUpdateRequired: true } });
});
and
Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const anonClient = createClient<Database>(supabaseUrl, anonKey, options);

// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('update-system-settings-handler', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

// log the error message
const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const anonClient = createClient<Database>(supabaseUrl, anonKey, options);

// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('update-system-settings-handler', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

// log the error message
const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
inder
inder•4mo ago
still same issue? or some other error
SB
SBOP•4mo ago
same issue
SB
SBOP•4mo ago
as soon as i introduce this one line const handler = createHandler(mockTasks);
No description
inder
inder•4mo ago
And if you only run this one, does that work?
SB
SBOP•4mo ago
this has been working for a few months now. wednesday, before i made any changes i checked to ensure all the tests and passed and was faced with this issue
SB
SBOP•4mo ago
for reference, this works
No description
SB
SBOP•4mo ago
adding the line and it fails
No description
inder
inder•4mo ago
I've updated my test to use mock request as you're doing but I still don't get any errors. Here is the code, the only difference is I'm not passing in mock tasks.
import { createClient } from 'npm:@supabase/supabase-js@2'
import { assert,assertEquals } from "jsr:@std/assert";
import {testFn} from "./functions/hello/index.ts";

const options = {
auth: {
autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false
}
};

Deno.test('[Unit Test] Method IS POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
const handler = testFn()
const mockReq = new Request("http://localhost",{method:"POST",body:JSON.stringify({})});
const res = await handler(mockReq);
assertEquals(res.status,200)
});


Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const supabaseUrl = "http://localhost:54321";
const anonKey="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";

const anonClient = createClient(supabaseUrl, anonKey,options);
// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('hello', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
import { createClient } from 'npm:@supabase/supabase-js@2'
import { assert,assertEquals } from "jsr:@std/assert";
import {testFn} from "./functions/hello/index.ts";

const options = {
auth: {
autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false
}
};

Deno.test('[Unit Test] Method IS POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
const handler = testFn()
const mockReq = new Request("http://localhost",{method:"POST",body:JSON.stringify({})});
const res = await handler(mockReq);
assertEquals(res.status,200)
});


Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// Switch to anon client
const supabaseUrl = "http://localhost:54321";
const anonKey="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";

const anonClient = createClient(supabaseUrl, anonKey,options);
// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('hello', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
I've deno 2.4.0 installed.
SB
SBOP•4mo ago
ive done a little more digging and i think it might be related to the end point not being ready. adding a 1 sec timeout seems to resolve the problem. any thoughts on that?
Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// add a timeout of 1 seconds
await new Promise(resolve => setTimeout(resolve, 1000));

// Switch to anon client
const anonClient = createClient<Database>(supabaseUrl, anonKey, options);

// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('update-system-settings-handler', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

// log the error message
const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
Deno.test('[Unit Test] Method Not POST', {sanitizeOps:false, sanitizeResources:false}, async () => {
// add a timeout of 1 seconds
await new Promise(resolve => setTimeout(resolve, 1000));

// Switch to anon client
const anonClient = createClient<Database>(supabaseUrl, anonKey, options);

// Invoke the 'order' function without the first name
const { data, error } = await anonClient.functions.invoke('update-system-settings-handler', {
method: 'PUT',
headers: { Authorization: `Bearer ${anonKey}`},
body: { }
});

// log the error message
const errorMessage = (await error.context.json()).error;
console.log(`message: ${errorMessage}`);
console.log(`status: ${await error.context.status}`);
console.log(`headers: ${await error.context.headers.get('Allow')}`);

// Check for errors from the function invocation
if (data || !error || errorMessage != 'Method not allowed' || error.context.status != 405 || error.context.headers.get('Allow') != 'POST') {
assert(false);
}
});
inder
inder•4mo ago
Vitest has a built in retry mechanism. I don't usually use deno tests but if deno test has this functionality then use that instead of setting a hardcoded timeout
SB
SBOP•4mo ago
checking the service is available works which is odd
No description
inder
inder•4mo ago
I think this is functioning the same way as setting a timeout, just some extra ms before and that seems to do the job. But i didn't have to do any of this. I ran all tests on Linux. Are you on Linux? Try adding a retry mechanism https://docs.deno.com/examples/exponential_backoff/
SB
SBOP•4mo ago
im running on mac using devcontainers
SB
SBOP•3mo ago
@inder im still seeing this error, i see a lot of 502 responses from the edge functions but once you broswer to the end point it seems to work. Function (http://localhost:54321/functions/v1/update-system-settings-handler) reachable: 502 message: undefined status: 502 headers: null Retry 1 failed. Function (http://localhost:54321/functions/v1/update-system-settings-handler) reachable: 401 message: Method not allowed status: 405 headers: POST when i run this in my github action, i get the following
No description
No description
inder
inder•3mo ago
This is on deno version 2.4.0? and the cli version?
SB
SBOP•3mo ago
this my action edge-functions-tests: name: Edge Functions Testing runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Supabase CLI uses: supabase/setup-cli@v1 with: version: latest - name: Setup Deno uses: denoland/setup-deno@v2 with: deno-version: latest - name: Pin Postgres version run: | mkdir -p supabase/.temp echo "15.8.1.054" > supabase/.temp/postgres-version - name: Start Supabase run: | supabase start - name: Serve edge functions run: | supabase functions serve \ --env-file=./supabase/functions/.env.test \ --import-map=./deno.json \ | tee supabase-functions.log & echo "Serving edge functions… pid=$!"
# - name: Wait for edge functions to be ready # run: | # echo "Waiting for Supabase Edge Functions to be ready..." # until grep -q "Using supabase-edge-runtime-" supabase-functions.log; do # sleep 0.5 # done # echo "Supabase Edge Functions are ready!" - name: Wait for edge functions to start run: sleep 10 - name: Run edge-function tests run: deno test --allow-all --env=./.env.test.supabase ./test/update-system-settings-handler-test.ts working-directory: ./supabase/functions this my action
edge-functions-tests:
name: Edge Functions Testing
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest

- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: latest

- name: Pin Postgres version
run: |
mkdir -p supabase/.temp
echo "15.8.1.054" > supabase/.temp/postgres-version

- name: Start Supabase
run: |
supabase start

- name: Serve edge functions
run: |
supabase functions serve \
--env-file=./supabase/functions/.env.test \
--import-map=./deno.json \
| tee supabase-functions.log &
echo "Serving edge functions… pid=$!"

# - name: Wait for edge functions to be ready
# run: |
# echo "Waiting for Supabase Edge Functions to be ready..."
# until grep -q "Using supabase-edge-runtime-" supabase-functions.log; do
# sleep 0.5
# done
# echo "Supabase Edge Functions are ready!"

- name: Wait for edge functions to start
run: sleep 10

- name: Run edge-function tests
run: deno test --allow-all --env=./.env.test.supabase ./test/update-system-settings-handler-test.ts
working-directory: ./supabase/functions
edge-functions-tests:
name: Edge Functions Testing
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest

- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: latest

- name: Pin Postgres version
run: |
mkdir -p supabase/.temp
echo "15.8.1.054" > supabase/.temp/postgres-version

- name: Start Supabase
run: |
supabase start

- name: Serve edge functions
run: |
supabase functions serve \
--env-file=./supabase/functions/.env.test \
--import-map=./deno.json \
| tee supabase-functions.log &
echo "Serving edge functions… pid=$!"

# - name: Wait for edge functions to be ready
# run: |
# echo "Waiting for Supabase Edge Functions to be ready..."
# until grep -q "Using supabase-edge-runtime-" supabase-functions.log; do
# sleep 0.5
# done
# echo "Supabase Edge Functions are ready!"

- name: Wait for edge functions to start
run: sleep 10

- name: Run edge-function tests
run: deno test --allow-all --env=./.env.test.supabase ./test/update-system-settings-handler-test.ts
working-directory: ./supabase/functions
inder
inder•3mo ago
I'll take a look You're running 2 tests right? POST and NOT POST
SB
SBOP•3mo ago
yes
SB
SBOP•3mo ago
this is the test class and function, plus cors
inder
inder•3mo ago
@SB What version of @trigger.dev/sdk are you using? When I try to import tasks from sdk I get error.
SB
SBOP•3mo ago
"@trigger.dev/sdk": "npm:@trigger.dev/sdk/v3" Is this the version you are using
inder
inder•3mo ago
Its using 3.3.17
No description
SB
SBOP•3mo ago
Download https://registry.npmjs.org/@trigger.dev/sdk/-/sdk-3.3.17.tgz this is the same version as me
inder
inder•3mo ago
But that's what its using
SB
SBOP•3mo ago
You need to use npm:@trigger.dev/sdk/v3
inder
inder•3mo ago
https://github.com/singh-inder/deno-supa-test/actions/runs/16179861642/job/45673751770 I hardcoded the supabase values that's why I removed the env flag from deno and supabase
SB
SBOP•3mo ago
I copied exactly as you did and it works. Thanks I guess my next question is how can you pass environment variables into the setting up of the functions, like I was doing with the serve? What is the cause of this? Two weeks ago this worked with no issues.
inder
inder•3mo ago
Two weeks ago deno was on v2.3.7, so maybe with v2.4 something was changed. You'll have to go though their changelog If testing with deno is not a requirement, you can try bun For envs, I've pushed a new commit. You can do something like this
SB
SBOP•3mo ago
@inder just for completeness and possibly for anyone else with this issue. i spent the morning stepping through every difference between how you had setup the tests and how i had done it. below is the final yaml that is now working.
edge-functions-tests:
name: Edge Functions Testing
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest

- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: latest

- name: Pin Postgres version
run: |
mkdir -p supabase/.temp
echo "15.8.1.054" > supabase/.temp/postgres-version

- name: Start Supabase
run: supabase start

- name: Serve edge functions
run: |
nohup supabase functions serve \
--env-file ./supabase/functions/.env.test \
--import-map ./deno.json \
--debug &

- name: Wait for edge functions to start
run: sleep 5

- name: Run update-system-settings-handler edge-function tests
run: deno test --allow-all --env=./.env.test.supabase ./test/*
working-directory: ./supabase/functions

- name: Get edge-runtime logs
run: docker logs supabase_edge_runtime_"$(yq '.project_id' supabase/config.toml)"
edge-functions-tests:
name: Edge Functions Testing
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest

- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: latest

- name: Pin Postgres version
run: |
mkdir -p supabase/.temp
echo "15.8.1.054" > supabase/.temp/postgres-version

- name: Start Supabase
run: supabase start

- name: Serve edge functions
run: |
nohup supabase functions serve \
--env-file ./supabase/functions/.env.test \
--import-map ./deno.json \
--debug &

- name: Wait for edge functions to start
run: sleep 5

- name: Run update-system-settings-handler edge-function tests
run: deno test --allow-all --env=./.env.test.supabase ./test/*
working-directory: ./supabase/functions

- name: Get edge-runtime logs
run: docker logs supabase_edge_runtime_"$(yq '.project_id' supabase/config.toml)"
one thing i did notice was that without import map in the serve, i seems to get similar errors whereby the end point was reachable with a 401 status but the response when invoked with 503
- name: Serve edge functions
run: |
nohup supabase functions serve \
--env-file ./supabase/functions/.env.test \
--import-map ./deno.json \
--debug &
- name: Serve edge functions
run: |
nohup supabase functions serve \
--env-file ./supabase/functions/.env.test \
--import-map ./deno.json \
--debug &
All my tests remained the same (I remove the retries and anything added from this thread) one the biggest changes i made was split the database and edge function tests, im still a lost though as to what changed between the above working yaml and the old version which broke (shared below)
database-test:
name: DB Testing
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest

- name: Overwrite Supabase db version [Required since 15.8.1.060 has breaking changes]
run: |
mkdir -p supabase/.temp
echo "15.8.1.054" > supabase/.temp/postgres-version

- name: Start Supabase local development setup
run: supabase start

- name: Run database schema tests
run: supabase test db

- name: Verify generated types are up-to-date
run: |
supabase gen types typescript --local > types.ts
if [ "$(git diff --ignore-space-at-eol types.ts | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:"
git diff
exit 1
fi

- name: Setup Deno
uses: denoland/setup-deno@v2

- name: Serve edge functions
run: nohup supabase functions serve --env-file ./supabase/functions/.env.test --import-map ./deno.json --debug > nohup.out 2> nohup.err < /dev/null &

- name: Wait for edge functions to start
run: sleep 10

- name: Run edge function tests
run: deno test --allow-all --env=./.env.test.supabase ./test/*
working-directory: ./supabase/functions
database-test:
name: DB Testing
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest

- name: Overwrite Supabase db version [Required since 15.8.1.060 has breaking changes]
run: |
mkdir -p supabase/.temp
echo "15.8.1.054" > supabase/.temp/postgres-version

- name: Start Supabase local development setup
run: supabase start

- name: Run database schema tests
run: supabase test db

- name: Verify generated types are up-to-date
run: |
supabase gen types typescript --local > types.ts
if [ "$(git diff --ignore-space-at-eol types.ts | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:"
git diff
exit 1
fi

- name: Setup Deno
uses: denoland/setup-deno@v2

- name: Serve edge functions
run: nohup supabase functions serve --env-file ./supabase/functions/.env.test --import-map ./deno.json --debug > nohup.out 2> nohup.err < /dev/null &

- name: Wait for edge functions to start
run: sleep 10

- name: Run edge function tests
run: deno test --allow-all --env=./.env.test.supabase ./test/*
working-directory: ./supabase/functions
inder
inder•3mo ago
Thankyou for posting your solution.

Did you find this page helpful?