Skip to content

Commit

Permalink
Merge pull request #3 from FantasyTeddy/local-test
Browse files Browse the repository at this point in the history
Enable local test execution with `docker-compose`
  • Loading branch information
acupofjose authored Jan 4, 2024
2 parents 0ccb158 + 081e1eb commit 165ad39
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 8 deletions.
7 changes: 3 additions & 4 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ jobs:
build-and-test:
runs-on: ubuntu-latest

env:
TOKEN: ${{ secrets.TOKEN }}
FUNCTION_ENDPOINT: ${{ secrets.FUNCTION_ENDPOINT }}

steps:
- uses: actions/checkout@v3

Expand All @@ -28,5 +24,8 @@ jobs:
- name: Build
run: dotnet build --configuration Release --no-restore

- name: Initialize Testing Stack
run: docker-compose up -d

- name: Test
run: dotnet test --no-restore
23 changes: 19 additions & 4 deletions FunctionsTests/ClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Supabase.Functions;
using static Supabase.Functions.Client;
Expand All @@ -16,10 +19,8 @@ public class ClientTests
[TestInitialize]
public void Initialize()
{
var endpoint = Environment.GetEnvironmentVariable("FUNCTION_ENDPOINT");

_token = Environment.GetEnvironmentVariable("TOKEN");
_client = new Client(endpoint);
_token = GenerateToken("37c304f8-51aa-419a-a1af-06154e63707a");
_client = new Client("http://localhost:9000");
}

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

Assert.IsInstanceOfType(bytes, typeof(byte[]));
}

private static string GenerateToken(string secret)
{
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));

var tokenDescriptor = new SecurityTokenDescriptor
{
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256Signature)
};

var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(securityToken);
}
}
}
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,10 @@ Join the ranks! See a problem? Help fix it!
## Contributing

We are more than happy to have contributions! Please submit a PR.

### Testing

To run the tests locally you must have docker and docker-compose installed. Then in the root of the repository run:

- `docker-compose up -d`
- `dotnet test`
17 changes: 17 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: "3"

services:
functions:
image: supabase/edge-runtime:v1.30.0
ports:
- "9000:9000"
environment:
JWT_SECRET: "37c304f8-51aa-419a-a1af-06154e63707a"
VERIFY_JWT: "true"
volumes:
- ./supabase/functions:/home/deno/functions:Z
command:
- start
- --main-service
- /home/deno/functions/main
restart: unless-stopped
94 changes: 94 additions & 0 deletions supabase/functions/main/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'

console.log('main function started')

const JWT_SECRET = Deno.env.get('JWT_SECRET')
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'

function getAuthToken(req: Request) {
const authHeader = req.headers.get('authorization')
if (!authHeader) {
throw new Error('Missing authorization header')
}
const [bearer, token] = authHeader.split(' ')
if (bearer !== 'Bearer') {
throw new Error(`Auth header is not 'Bearer {token}'`)
}
return token
}

async function verifyJWT(jwt: string): Promise<boolean> {
const encoder = new TextEncoder()
const secretKey = encoder.encode(JWT_SECRET)
try {
await jose.jwtVerify(jwt, secretKey)
} catch (err) {
console.error(err)
return false
}
return true
}

serve(async (req: Request) => {
if (req.method !== 'OPTIONS' && VERIFY_JWT) {
try {
const token = getAuthToken(req)
const isValidJWT = await verifyJWT(token)

if (!isValidJWT) {
return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
})
}
} catch (e) {
console.error(e)
return new Response(JSON.stringify({ msg: e.toString() }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
})
}
}

const url = new URL(req.url)
const { pathname } = url
const path_parts = pathname.split('/')
const service_name = path_parts[1]

if (!service_name || service_name === '') {
const error = { msg: 'missing function name in request' }
return new Response(JSON.stringify(error), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}

const servicePath = `/home/deno/functions/${service_name}`
console.error(`serving the request with ${servicePath}`)

const memoryLimitMb = 150
const workerTimeoutMs = 1 * 60 * 1000
const noModuleCache = false
const importMapPath = null
const envVarsObj = Deno.env.toObject()
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])

try {
const worker = await EdgeRuntime.userWorkers.create({
servicePath,
memoryLimitMb,
workerTimeoutMs,
noModuleCache,
importMapPath,
envVars,
})
return await worker.fetch(req)
} catch (e) {
const error = { msg: e.toString() }
return new Response(JSON.stringify(error), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
})

0 comments on commit 165ad39

Please sign in to comment.