Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/backend/bun.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@ const config: BunTestConfig = {
};

export default config;

165 changes: 93 additions & 72 deletions apps/backend/src/config/supabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ const createMockSupabase = () => {
wallet_users: new Map(),
users: new Map(),
properties: new Map(),
bookings: new Map()
bookings: new Map(),
};

// Helper function to get mock data for each table
Expand All @@ -218,9 +218,9 @@ const createMockSupabase = () => {
public_key: 'test-public-key',
challenge: 'test-challenge-value',
expires_at: new Date(Date.now() + 300000).toISOString(), // 5 minutes from now
created_at: new Date().toISOString()
created_at: new Date().toISOString(),
};

case 'bookings':
return {
id: '123e4567-e89b-12d3-a456-426614174555',
Expand All @@ -233,21 +233,21 @@ const createMockSupabase = () => {
total: 100,
status: 'pending',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
updated_at: new Date().toISOString(),
};

default:
return null;
}
};

const createMockChain = (tableName: string) => {
const filters: Array<{column: string, value: any, operator: string}> = [];
const filters: Array<{ column: string; value: any; operator: string }> = [];

const chain = {
_tableName: tableName,
_filters: filters,

// Query methods
select: () => chain,
eq: (column: string, value: any) => {
Expand All @@ -258,65 +258,83 @@ const createMockSupabase = () => {
filters.push({ column, value, operator: 'gt' });
return chain;
},

// Unused filter methods - just return chain for compatibility
lt: () => chain, gte: () => chain, lte: () => chain, neq: () => chain,
like: () => chain, ilike: () => chain, is: () => chain, in: () => chain,
contains: () => chain, containedBy: () => chain, rangeGt: () => chain,
rangeGte: () => chain, rangeLt: () => chain, rangeLte: () => chain,
rangeAdjacent: () => chain, overlaps: () => chain, textSearch: () => chain,
match: () => chain, not: () => chain, or: () => chain, filter: () => chain,
order: () => chain, limit: () => chain, range: () => chain, abortSignal: () => chain,
lt: () => chain,
gte: () => chain,
lte: () => chain,
neq: () => chain,
like: () => chain,
ilike: () => chain,
is: () => chain,
in: () => chain,
contains: () => chain,
containedBy: () => chain,
rangeGt: () => chain,
rangeGte: () => chain,
rangeLt: () => chain,
rangeLte: () => chain,
rangeAdjacent: () => chain,
overlaps: () => chain,
textSearch: () => chain,
match: () => chain,
not: () => chain,
or: () => chain,
filter: () => chain,
order: () => chain,
limit: () => chain,
range: () => chain,
abortSignal: () => chain,
single: () => {
// Handle special filter cases
if (filters.length > 0) {
const hasNonExistentChallenge = filters.some(f =>
f.column === 'challenge' && f.value === 'non-existent-challenge'
const hasNonExistentChallenge = filters.some(
(f) => f.column === 'challenge' && f.value === 'non-existent-challenge'
);
if (hasNonExistentChallenge) {
return Promise.resolve({ data: null, error: null });
}
const hasExpiredChallenge = filters.some(f =>
f.column === 'expires_at' && f.operator === 'gt' &&
new Date(f.value) < new Date()

const hasExpiredChallenge = filters.some(
(f) =>
f.column === 'expires_at' && f.operator === 'gt' && new Date(f.value) < new Date()
);
if (hasExpiredChallenge) {
return Promise.resolve({ data: null, error: null });
}
}

// Return mock data based on table
return Promise.resolve({
data: getMockDataForTable(tableName),
error: null
return Promise.resolve({
data: getMockDataForTable(tableName),
error: null,
});
},
maybeSingle: () => Promise.resolve({ data: null, error: null }),
then: (callback: any) => {
// Handle special filter cases
if (filters.length > 0) {
const hasNonExistentChallenge = filters.some(f =>
f.column === 'challenge' && f.value === 'non-existent-challenge'
const hasNonExistentChallenge = filters.some(
(f) => f.column === 'challenge' && f.value === 'non-existent-challenge'
);
if (hasNonExistentChallenge) {
return callback({ data: [], error: null });
}
const hasExpiredChallenge = filters.some(f =>
f.column === 'expires_at' && f.operator === 'gt' &&
new Date(f.value) < new Date()

const hasExpiredChallenge = filters.some(
(f) =>
f.column === 'expires_at' && f.operator === 'gt' && new Date(f.value) < new Date()
);
if (hasExpiredChallenge) {
return callback({ data: [], error: null });
}
}

// Return mock data as array
const mockData = getMockDataForTable(tableName);
const data = mockData ? [mockData] : [];
return callback({ data, error: null });
}
},
};
return chain;
};
Expand All @@ -341,7 +359,7 @@ const createMockSupabase = () => {
mockData[tableName].set(id, record);
}
return callback({ data: [record], error: null });
}
},
}),
single: () => {
const id = `mock-${Date.now()}`;
Expand All @@ -358,14 +376,14 @@ const createMockSupabase = () => {
mockData[tableName].set(id, record);
}
return callback({ data: [record], error: null });
}
},
};
return insertChain;
},
update: () => createMockChain(tableName),
upsert: () => createMockChain(tableName),
delete: () => createMockChain(tableName),
then: (callback: any) => callback({ data: [], error: null })
then: (callback: any) => callback({ data: [], error: null }),
});

return {
Expand All @@ -374,47 +392,50 @@ const createMockSupabase = () => {
getUser: (token: string) => {
// Return error for invalid tokens
if (token === 'invalid.token') {
return Promise.resolve({
data: { user: null },
error: { message: 'Invalid token' }
return Promise.resolve({
data: { user: null },
error: { message: 'Invalid token' },
});
}
return Promise.resolve({
data: { user: { id: 'test-user-id', email: 'test@example.com' } },
error: null
return Promise.resolve({
data: { user: { id: 'test-user-id', email: 'test@example.com' } },
error: null,
});
},
signInWithPassword: () => Promise.resolve({
data: { user: { id: 'test-user-id', email: 'test@example.com' }, session: null },
error: null
}),
signUp: () => Promise.resolve({
data: { user: { id: 'test-user-id', email: 'test@example.com' }, session: null },
error: null
})
}
signInWithPassword: () =>
Promise.resolve({
data: { user: { id: 'test-user-id', email: 'test@example.com' }, session: null },
error: null,
}),
signUp: () =>
Promise.resolve({
data: { user: { id: 'test-user-id', email: 'test@example.com' }, session: null },
error: null,
}),
},
};
};

// Use mock in test environment, real client otherwise
export const supabase = process.env.NODE_ENV === 'test'
? createMockSupabase() as any
: (() => {
if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
throw new Error('Missing SUPABASE_SERVICE_ROLE_KEY environment variable');
}

return createClient<Database>(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_ROLE_KEY,
{
auth: {
autoRefreshToken: false,
persistSession: false,
},
db: {
schema: 'public',
},
export const supabase =
process.env.NODE_ENV === 'test'
? (createMockSupabase() as any)
: (() => {
if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
throw new Error('Missing SUPABASE_SERVICE_ROLE_KEY environment variable');
}
);
})();

return createClient<Database>(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_ROLE_KEY,
{
auth: {
autoRefreshToken: false,
persistSession: false,
},
db: {
schema: 'public',
},
}
);
})();
8 changes: 4 additions & 4 deletions apps/backend/src/controllers/booking.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const getBooking = async (req: AuthRequest, res: Response) => {
// Handle generic errors from mocks or other sources
if (error instanceof Error) {
const errorMessage = error.message;

// Map common error messages to appropriate status codes
if (errorMessage === 'Access denied') {
return res.status(403).json({
Expand All @@ -124,7 +124,7 @@ export const getBooking = async (req: AuthRequest, res: Response) => {
},
});
}

if (errorMessage === 'Booking not found') {
return res.status(404).json({
success: false,
Expand All @@ -135,7 +135,7 @@ export const getBooking = async (req: AuthRequest, res: Response) => {
},
});
}

if (errorMessage === 'Property not found') {
return res.status(404).json({
success: false,
Expand All @@ -146,7 +146,7 @@ export const getBooking = async (req: AuthRequest, res: Response) => {
},
});
}

if (errorMessage === 'Host user not found') {
return res.status(404).json({
success: false,
Expand Down
4 changes: 2 additions & 2 deletions apps/backend/src/services/sync.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,11 +338,11 @@ export class SyncService {
} catch (error) {
console.error(`Attempt ${attempt} failed to get current block height:`, error);
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, retryDelay));
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
}
}

console.warn('All retries failed. Returning last known block.');
return this.lastProcessedBlock;
}
Expand Down
32 changes: 18 additions & 14 deletions apps/backend/tests/integration/booking-integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'bun:test';
import express from 'express';
import request from 'supertest';
import type { Response } from 'supertest';
import { describe, it, expect, beforeEach, afterEach, beforeAll } from 'bun:test';
import {
createConflictBookingInput,
createInvalidBookingInput,
Expand All @@ -22,33 +22,35 @@ mock.module('../../src/config/supabase', () => ({
select: mock(() => ({
eq: mock(() => ({
single: mock(() => Promise.resolve({ data: null, error: null })),
then: mock((callback: any) => callback({ data: [], error: null }))
then: mock((callback: any) => callback({ data: [], error: null })),
})),
then: mock((callback: any) => callback({ data: [], error: null }))
then: mock((callback: any) => callback({ data: [], error: null })),
})),
insert: mock(() => ({
select: mock(() => Promise.resolve({ data: [], error: null })),
then: mock((callback: any) => callback({ data: [], error: null }))
then: mock((callback: any) => callback({ data: [], error: null })),
})),
update: mock(() => ({
eq: mock(() => Promise.resolve({ data: [], error: null })),
then: mock((callback: any) => callback({ data: [], error: null }))
then: mock((callback: any) => callback({ data: [], error: null })),
})),
delete: mock(() => ({
eq: mock(() => Promise.resolve({ data: [], error: null })),
then: mock((callback: any) => callback({ data: [], error: null }))
then: mock((callback: any) => callback({ data: [], error: null })),
})),
upsert: mock(() => ({
eq: mock(() => Promise.resolve({ data: [], error: null })),
then: mock((callback: any) => callback({ data: [], error: null }))
then: mock((callback: any) => callback({ data: [], error: null })),
})),
then: mock((callback: any) => callback({ data: [], error: null }))
then: mock((callback: any) => callback({ data: [], error: null })),
})),
auth: {
getUser: mock(() => Promise.resolve({
data: { user: { id: 'test-user-id', email: 'test@example.com' } },
error: null,
})),
getUser: mock(() =>
Promise.resolve({
data: { user: { id: 'test-user-id', email: 'test@example.com' } },
error: null,
})
),
},
},
}));
Expand Down Expand Up @@ -348,8 +350,10 @@ describe('Booking Integration Tests', () => {
supabase.from.mockReturnValue({
insert: mock(() => ({
select: mock(() => ({
single: mock(() => Promise.resolve({ data: null, error: { message: 'Database error' } }))
}))
single: mock(() =>
Promise.resolve({ data: null, error: { message: 'Database error' } })
),
})),
})),
});

Expand Down
Loading