chore: full stack stability and migration fixes, plus react UI progress
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
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()}`;
|
||||
@@ -48,7 +52,7 @@ describe('Edge Functions', () => {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${process.env.MADBASE_ANON_KEY}`
|
||||
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
|
||||
},
|
||||
body: JSON.stringify({ payload: { name: 'World' } })
|
||||
});
|
||||
@@ -96,7 +100,7 @@ describe('Edge Functions', () => {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${process.env.MADBASE_ANON_KEY}`
|
||||
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
|
||||
},
|
||||
body: JSON.stringify({ payload: { name: 'World' } })
|
||||
});
|
||||
@@ -140,9 +144,9 @@ describe('Edge Functions', () => {
|
||||
// Invoke
|
||||
const res = await fetch(`${process.env.MADBASE_URL}/functions/v1/${name}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${process.env.MADBASE_ANON_KEY}`
|
||||
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
|
||||
},
|
||||
body: JSON.stringify({ payload: {} })
|
||||
});
|
||||
@@ -181,9 +185,9 @@ describe('Edge Functions', () => {
|
||||
// Invoke (Missing Field)
|
||||
const res = await fetch(`${process.env.MADBASE_URL}/functions/v1/${name}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${process.env.MADBASE_ANON_KEY}`
|
||||
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
|
||||
},
|
||||
body: JSON.stringify({ payload: {} })
|
||||
});
|
||||
@@ -230,9 +234,9 @@ describe('Edge Functions', () => {
|
||||
|
||||
const res = await fetch(`${process.env.MADBASE_URL}/functions/v1/${name}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${process.env.MADBASE_ANON_KEY}`
|
||||
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
|
||||
},
|
||||
body: JSON.stringify({ payload: {} })
|
||||
});
|
||||
@@ -285,9 +289,9 @@ describe('Edge Functions', () => {
|
||||
|
||||
const res = await fetch(`${process.env.MADBASE_URL}/functions/v1/${name}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${process.env.MADBASE_ANON_KEY}`
|
||||
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
|
||||
},
|
||||
body: JSON.stringify({ payload: { email: "newuser@example.com" } })
|
||||
});
|
||||
@@ -409,7 +413,7 @@ describe('Edge Functions', () => {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${process.env.MADBASE_ANON_KEY}`
|
||||
'Authorization': `Bearer ${process.env.MADBASE_SERVICE_ROLE_KEY}`
|
||||
},
|
||||
body: JSON.stringify({ payload: { productId: "prod_123", successUrl: "http://example.com" } })
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import jwt from 'jsonwebtoken';
|
||||
|
||||
describe('Generate Keys', () => {
|
||||
it('should generate keys', () => {
|
||||
const secret = 'testsecret';
|
||||
const secret = 'supersecret1234567890123456789012';
|
||||
const anon = jwt.sign({ role: 'anon', iss: 'madbase' }, secret, { algorithm: 'HS256' });
|
||||
const service = jwt.sign({ role: 'service_role', iss: 'madbase' }, secret, { algorithm: 'HS256' });
|
||||
console.log(`ANON_KEY=${anon}`);
|
||||
|
||||
@@ -5,13 +5,17 @@ const client = createAnonClient();
|
||||
|
||||
describe('Realtime', () => {
|
||||
it('should resume subscription from last_event_id', async () => {
|
||||
console.log('Starting realtime test');
|
||||
|
||||
// 1. Create a message while no one is listening
|
||||
console.log('Step 1: Creating test record');
|
||||
const { data: inserted, error } = await client
|
||||
.from('todos')
|
||||
.insert({ title: 'Missed Event', completed: false })
|
||||
.select()
|
||||
.single();
|
||||
expect(error).toBeNull();
|
||||
console.log('Created record:', inserted);
|
||||
|
||||
// We need to know the ID of this event in realtime history.
|
||||
// Ideally we query `madbase_realtime.messages` but client can't.
|
||||
@@ -20,6 +24,7 @@ describe('Realtime', () => {
|
||||
// Let's assume we want everything after ID=0.
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
console.log('Step 2: Creating channel with last_event_id=0');
|
||||
// 2. Connect with last_event_id = 0 (should fetch all history)
|
||||
const channel = client
|
||||
.channel('public:todos', { config: { last_event_id: 0 } as any })
|
||||
@@ -35,11 +40,21 @@ describe('Realtime', () => {
|
||||
}
|
||||
)
|
||||
.subscribe((status, err) => {
|
||||
console.log('Channel status:', status, 'Error:', err);
|
||||
if (status === 'SUBSCRIBED') {
|
||||
console.log('Subscribed with resume');
|
||||
}
|
||||
if (status === 'CHANNEL_ERROR') {
|
||||
reject(err);
|
||||
console.error('Channel error:', err);
|
||||
reject(err || new Error('Unknown channel error'));
|
||||
}
|
||||
if (status === 'TIMED_OUT') {
|
||||
console.error('Channel timeout');
|
||||
reject(new Error('Channel connection timeout'));
|
||||
}
|
||||
if (status === 'CLOSED') {
|
||||
console.error('Channel closed');
|
||||
reject(new Error('Channel closed unexpectedly'));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
|
||||
dotenv.config({ path: path.resolve(process.cwd(), '.env') });
|
||||
dotenv.config({ path: path.resolve(process.cwd(), '../../.env') });
|
||||
|
||||
const SUPABASE_URL = process.env.MADBASE_URL || 'http://localhost:8000';
|
||||
const SUPABASE_ANON_KEY = process.env.MADBASE_ANON_KEY || '';
|
||||
@@ -18,6 +18,12 @@ export const createAnonClient = (): SupabaseClient => {
|
||||
persistSession: false,
|
||||
autoRefreshToken: false,
|
||||
},
|
||||
global: {
|
||||
headers: {
|
||||
'x-project-ref': 'default',
|
||||
},
|
||||
},
|
||||
accessToken: async () => SUPABASE_ANON_KEY,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
24
tests/integration/test_direct_ws.js
Normal file
24
tests/integration/test_direct_ws.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import WebSocket from 'ws';
|
||||
const headers = { 'apikey': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbm9uIiwicm9sZSI6ImFub24iLCJpc3MiOiJtYWRiYXNlIiwiaWF0IjoxNzczNjk0OTEwLCJleHAiOjE3NzQyOTk3MTB9.kiDrLssL7YrvQdiOvhbH6qsvcO_O2cc4v6i5s2zN3wM' };
|
||||
const ws = new WebSocket('ws://localhost:8002/realtime/v1/websocket', { headers });
|
||||
ws.on('open', () => {
|
||||
console.log('WebSocket connected');
|
||||
ws.send(JSON.stringify(['1', '1', 'realtime:public:todos', 'phx_join', { config: { access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbm9uIiwicm9sZSI6ImFub24iLCJpc3MiOiJtYWRiYXNlIiwiaWF0IjoxNzczNjk0OTEwLCJleHAiOjE3NzQyOTk3MTB9.kiDrLssL7YrvQdiOvhbH6qsvcO_O2cc4v6i5s2zN3wM', postgres_changes: [{ schema: 'public', table: 'todos' }] } }]));
|
||||
});
|
||||
ws.on('message', (data) => {
|
||||
console.log('Received message:', data.toString());
|
||||
});
|
||||
ws.on('error', (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
});
|
||||
ws.on('close', (code, reason) => {
|
||||
console.log('WebSocket closed:', code, reason.toString());
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.close();
|
||||
} else {
|
||||
console.error('WebSocket did not connect within 5 seconds');
|
||||
process.exit(1);
|
||||
}
|
||||
}, 5000);
|
||||
34
tests/integration/test_ws.js
Normal file
34
tests/integration/test_ws.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import WebSocket from 'ws';
|
||||
|
||||
const headers = {
|
||||
'apikey': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbm9uIiwicm9sZSI6ImFub24iLCJpc3MiOiJtYWRiYXNlIiwiaWF0IjoxNzczNjk0OTEwLCJleHAiOjE3NzQyOTk3MTB9.kiDrLssL7YrvQdiOvhbH6qsvcO_O2cc4v6i5s2zN3wM',
|
||||
'x-project-ref': 'default'
|
||||
};
|
||||
|
||||
const ws = new WebSocket('ws://localhost:8000/realtime/v1/websocket', { headers });
|
||||
|
||||
ws.on('open', () => {
|
||||
console.log('WebSocket connected');
|
||||
ws.send(JSON.stringify(['1', '1', 'realtime:public:todos', 'phx_join', { config: { access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbm9uIiwicm9sZSI6ImFub24iLCJpc3MiOiJtYWRiYXNlIiwiaWF0IjoxNzczNjk0OTEwLCJleHAiOjE3NzQyOTk3MTB9.kiDrLssL7YrvQdiOvhbH6qsvcO_O2cc4v6i5s2zN3wM', postgres_changes: [{ schema: 'public', table: 'todos' }] } }]));
|
||||
});
|
||||
|
||||
ws.on('message', (data) => {
|
||||
console.log('Received message:', data.toString());
|
||||
});
|
||||
|
||||
ws.on('error', (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
});
|
||||
|
||||
ws.on('close', (code, reason) => {
|
||||
console.log('WebSocket closed:', code, reason.toString());
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.close();
|
||||
} else {
|
||||
console.error('WebSocket did not connect within 5 seconds');
|
||||
process.exit(1);
|
||||
}
|
||||
}, 5000);
|
||||
Reference in New Issue
Block a user