Skip to content

Commit 165ad39

Browse files
authored
Merge pull request #3 from FantasyTeddy/local-test
Enable local test execution with `docker-compose`
2 parents 0ccb158 + 081e1eb commit 165ad39

File tree

5 files changed

+140
-8
lines changed

5 files changed

+140
-8
lines changed

.github/workflows/build-and-test.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ jobs:
1010
build-and-test:
1111
runs-on: ubuntu-latest
1212

13-
env:
14-
TOKEN: ${{ secrets.TOKEN }}
15-
FUNCTION_ENDPOINT: ${{ secrets.FUNCTION_ENDPOINT }}
16-
1713
steps:
1814
- uses: actions/checkout@v3
1915

@@ -28,5 +24,8 @@ jobs:
2824
- name: Build
2925
run: dotnet build --configuration Release --no-restore
3026

27+
- name: Initialize Testing Stack
28+
run: docker-compose up -d
29+
3130
- name: Test
3231
run: dotnet test --no-restore

FunctionsTests/ClientTests.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.IdentityModel.Tokens.Jwt;
4+
using System.Text;
35
using System.Threading.Tasks;
6+
using Microsoft.IdentityModel.Tokens;
47
using Microsoft.VisualStudio.TestTools.UnitTesting;
58
using Supabase.Functions;
69
using static Supabase.Functions.Client;
@@ -16,10 +19,8 @@ public class ClientTests
1619
[TestInitialize]
1720
public void Initialize()
1821
{
19-
var endpoint = Environment.GetEnvironmentVariable("FUNCTION_ENDPOINT");
20-
21-
_token = Environment.GetEnvironmentVariable("TOKEN");
22-
_client = new Client(endpoint);
22+
_token = GenerateToken("37c304f8-51aa-419a-a1af-06154e63707a");
23+
_client = new Client("http://localhost:9000");
2324
}
2425

2526
[TestMethod("Invokes a function.")]
@@ -63,5 +64,19 @@ public async Task Invokes()
6364

6465
Assert.IsInstanceOfType(bytes, typeof(byte[]));
6566
}
67+
68+
private static string GenerateToken(string secret)
69+
{
70+
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
71+
72+
var tokenDescriptor = new SecurityTokenDescriptor
73+
{
74+
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256Signature)
75+
};
76+
77+
var tokenHandler = new JwtSecurityTokenHandler();
78+
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
79+
return tokenHandler.WriteToken(securityToken);
80+
}
6681
}
6782
}

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,10 @@ Join the ranks! See a problem? Help fix it!
2626
## Contributing
2727

2828
We are more than happy to have contributions! Please submit a PR.
29+
30+
### Testing
31+
32+
To run the tests locally you must have docker and docker-compose installed. Then in the root of the repository run:
33+
34+
- `docker-compose up -d`
35+
- `dotnet test`

docker-compose.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
version: "3"
2+
3+
services:
4+
functions:
5+
image: supabase/edge-runtime:v1.30.0
6+
ports:
7+
- "9000:9000"
8+
environment:
9+
JWT_SECRET: "37c304f8-51aa-419a-a1af-06154e63707a"
10+
VERIFY_JWT: "true"
11+
volumes:
12+
- ./supabase/functions:/home/deno/functions:Z
13+
command:
14+
- start
15+
- --main-service
16+
- /home/deno/functions/main
17+
restart: unless-stopped

supabase/functions/main/index.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
2+
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
3+
4+
console.log('main function started')
5+
6+
const JWT_SECRET = Deno.env.get('JWT_SECRET')
7+
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
8+
9+
function getAuthToken(req: Request) {
10+
const authHeader = req.headers.get('authorization')
11+
if (!authHeader) {
12+
throw new Error('Missing authorization header')
13+
}
14+
const [bearer, token] = authHeader.split(' ')
15+
if (bearer !== 'Bearer') {
16+
throw new Error(`Auth header is not 'Bearer {token}'`)
17+
}
18+
return token
19+
}
20+
21+
async function verifyJWT(jwt: string): Promise<boolean> {
22+
const encoder = new TextEncoder()
23+
const secretKey = encoder.encode(JWT_SECRET)
24+
try {
25+
await jose.jwtVerify(jwt, secretKey)
26+
} catch (err) {
27+
console.error(err)
28+
return false
29+
}
30+
return true
31+
}
32+
33+
serve(async (req: Request) => {
34+
if (req.method !== 'OPTIONS' && VERIFY_JWT) {
35+
try {
36+
const token = getAuthToken(req)
37+
const isValidJWT = await verifyJWT(token)
38+
39+
if (!isValidJWT) {
40+
return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
41+
status: 401,
42+
headers: { 'Content-Type': 'application/json' },
43+
})
44+
}
45+
} catch (e) {
46+
console.error(e)
47+
return new Response(JSON.stringify({ msg: e.toString() }), {
48+
status: 401,
49+
headers: { 'Content-Type': 'application/json' },
50+
})
51+
}
52+
}
53+
54+
const url = new URL(req.url)
55+
const { pathname } = url
56+
const path_parts = pathname.split('/')
57+
const service_name = path_parts[1]
58+
59+
if (!service_name || service_name === '') {
60+
const error = { msg: 'missing function name in request' }
61+
return new Response(JSON.stringify(error), {
62+
status: 400,
63+
headers: { 'Content-Type': 'application/json' },
64+
})
65+
}
66+
67+
const servicePath = `/home/deno/functions/${service_name}`
68+
console.error(`serving the request with ${servicePath}`)
69+
70+
const memoryLimitMb = 150
71+
const workerTimeoutMs = 1 * 60 * 1000
72+
const noModuleCache = false
73+
const importMapPath = null
74+
const envVarsObj = Deno.env.toObject()
75+
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])
76+
77+
try {
78+
const worker = await EdgeRuntime.userWorkers.create({
79+
servicePath,
80+
memoryLimitMb,
81+
workerTimeoutMs,
82+
noModuleCache,
83+
importMapPath,
84+
envVars,
85+
})
86+
return await worker.fetch(req)
87+
} catch (e) {
88+
const error = { msg: e.toString() }
89+
return new Response(JSON.stringify(error), {
90+
status: 500,
91+
headers: { 'Content-Type': 'application/json' },
92+
})
93+
}
94+
})

0 commit comments

Comments
 (0)