mirror of https://github.com/buster-so/buster.git
unique ids
This commit is contained in:
parent
8b22aafa2c
commit
fb618736e3
|
@ -1,144 +0,0 @@
|
|||
import axios, { type AxiosError, AxiosHeaders, type InternalAxiosRequestConfig } from 'axios';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createAxiosInstance, defaultAxiosRequestHandler } from './createAxiosInstance';
|
||||
import { rustErrorHandler } from './errors';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('axios');
|
||||
vi.mock('./buster_rest/errors');
|
||||
vi.mock('./createServerInstance');
|
||||
vi.mock('@tanstack/react-query', () => ({
|
||||
isServer: false,
|
||||
}));
|
||||
|
||||
describe('createAxiosInstance', () => {
|
||||
const mockBaseURL = 'https://api.example.com';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(axios.create as any).mockReturnValue({
|
||||
interceptors: {
|
||||
response: { use: vi.fn() },
|
||||
request: { use: vi.fn() },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates an axios instance with correct configuration', () => {
|
||||
createAxiosInstance(mockBaseURL);
|
||||
|
||||
expect(axios.create).toHaveBeenCalledWith({
|
||||
baseURL: mockBaseURL,
|
||||
timeout: 120000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sets up response interceptors', () => {
|
||||
const mockInstance = {
|
||||
interceptors: {
|
||||
response: { use: vi.fn() },
|
||||
request: { use: vi.fn() },
|
||||
},
|
||||
};
|
||||
(axios.create as any).mockReturnValue(mockInstance);
|
||||
|
||||
createAxiosInstance(mockBaseURL);
|
||||
|
||||
expect(mockInstance.interceptors.response.use).toHaveBeenCalled();
|
||||
expect(mockInstance.interceptors.request.use).toHaveBeenCalledWith(defaultAxiosRequestHandler);
|
||||
});
|
||||
|
||||
it('handles errors in response interceptor', async () => {
|
||||
const mockError = new Error('API Error') as AxiosError;
|
||||
const mockInstance = {
|
||||
interceptors: {
|
||||
response: { use: vi.fn() },
|
||||
request: { use: vi.fn() },
|
||||
},
|
||||
};
|
||||
(axios.create as any).mockReturnValue(mockInstance);
|
||||
(rustErrorHandler as any).mockReturnValue('Processed Error');
|
||||
|
||||
// Get the error handler by capturing the second argument passed to use()
|
||||
createAxiosInstance(mockBaseURL);
|
||||
const errorHandler = mockInstance.interceptors.response.use.mock.calls[0][1];
|
||||
|
||||
// Test the error handler
|
||||
await expect(errorHandler(mockError)).rejects.toBe('Processed Error');
|
||||
expect(rustErrorHandler).toHaveBeenCalledWith(mockError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultAxiosRequestHandler', () => {
|
||||
const mockConfig: InternalAxiosRequestConfig = {
|
||||
headers: new AxiosHeaders(),
|
||||
method: 'get',
|
||||
url: 'test',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it('adds authorization header with token in client environment', async () => {
|
||||
const mockToken = 'test-token';
|
||||
const mockCheckTokenValidity = vi.fn().mockResolvedValue({
|
||||
access_token: mockToken,
|
||||
isTokenValid: true,
|
||||
});
|
||||
|
||||
const result = await defaultAxiosRequestHandler(mockConfig, {
|
||||
checkTokenValidity: () => Promise.resolve(mockCheckTokenValidity()),
|
||||
});
|
||||
|
||||
expect(result.headers.Authorization).toBe(`Bearer ${mockToken}`);
|
||||
});
|
||||
|
||||
it('throws error when token is empty', async () => {
|
||||
const mockCheckTokenValidity = vi.fn().mockResolvedValue({
|
||||
access_token: '',
|
||||
isTokenValid: false,
|
||||
});
|
||||
|
||||
// The function should throw an error when no token is available
|
||||
await expect(
|
||||
defaultAxiosRequestHandler(mockConfig, {
|
||||
checkTokenValidity: () => Promise.resolve(mockCheckTokenValidity()),
|
||||
})
|
||||
).rejects.toThrow('User authentication error - failed to get valid token');
|
||||
});
|
||||
|
||||
it('throws error when checkTokenValidity fails', async () => {
|
||||
const mockCheckTokenValidity = vi.fn().mockRejectedValue(new Error('Token validation failed'));
|
||||
|
||||
// The function should throw an error when token validation fails
|
||||
await expect(
|
||||
defaultAxiosRequestHandler(mockConfig, {
|
||||
checkTokenValidity: () => Promise.reject(mockCheckTokenValidity()),
|
||||
})
|
||||
).rejects.toThrow('User authentication error - failed to get valid token');
|
||||
});
|
||||
|
||||
it('preserves existing config properties', async () => {
|
||||
const originalConfig: InternalAxiosRequestConfig = {
|
||||
...mockConfig,
|
||||
timeout: 5000,
|
||||
baseURL: 'https://api.example.com',
|
||||
};
|
||||
|
||||
const result = await defaultAxiosRequestHandler(originalConfig, {
|
||||
checkTokenValidity: () =>
|
||||
Promise.resolve({
|
||||
access_token: 'token',
|
||||
isTokenValid: true,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result.timeout).toBe(5000);
|
||||
expect(result.baseURL).toBe('https://api.example.com');
|
||||
});
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
import type React from 'react';
|
||||
import React from 'react';
|
||||
|
||||
export const SupabaseIcon: React.FC<{
|
||||
onClick?: () => void;
|
||||
|
@ -6,6 +6,9 @@ export const SupabaseIcon: React.FC<{
|
|||
size?: number;
|
||||
className?: string;
|
||||
}> = (props) => {
|
||||
const uniqueId = React.useId();
|
||||
const gradientId = `paint0_linear-${uniqueId}`;
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
|
@ -18,11 +21,11 @@ export const SupabaseIcon: React.FC<{
|
|||
<title>Supabase</title>
|
||||
<path
|
||||
d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z"
|
||||
fill="url(#paint0_linear)"
|
||||
fill={`url(#${gradientId})`}
|
||||
/>
|
||||
<path
|
||||
d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z"
|
||||
fill="url(#paint1_linear)"
|
||||
fill={`url(#overlay-${uniqueId})`}
|
||||
fillOpacity="0.2"
|
||||
/>
|
||||
<path
|
||||
|
@ -31,7 +34,7 @@ export const SupabaseIcon: React.FC<{
|
|||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear"
|
||||
id={gradientId}
|
||||
x1="53.9738"
|
||||
y1="54.974"
|
||||
x2="94.1635"
|
||||
|
@ -42,7 +45,7 @@ export const SupabaseIcon: React.FC<{
|
|||
<stop offset="1" stopColor="#3ECF8E" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear"
|
||||
id={`overlay-${uniqueId}`}
|
||||
x1="36.1558"
|
||||
y1="30.578"
|
||||
x2="54.4844"
|
||||
|
|
|
@ -24,9 +24,10 @@ import {
|
|||
export const RootProviders: React.FC<PropsWithChildren<SupabaseContextType>> = ({
|
||||
children,
|
||||
user,
|
||||
accessToken,
|
||||
}) => {
|
||||
return (
|
||||
<SupabaseContextProvider user={user}>
|
||||
<SupabaseContextProvider user={user} accessToken={accessToken}>
|
||||
<BusterStyleProvider>{children}</BusterStyleProvider>
|
||||
{/* <BusterReactQueryProvider>
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
|
|
|
@ -9,14 +9,19 @@ export type SupabaseContextType = {
|
|||
id: string;
|
||||
is_anonymous: boolean;
|
||||
};
|
||||
accessToken: string;
|
||||
};
|
||||
|
||||
const supabase = getBrowserClient();
|
||||
const fiveMinutes = 5 * 60 * 1000;
|
||||
|
||||
const useSupabaseContextInternal = ({ user }: SupabaseContextType) => {
|
||||
const useSupabaseContextInternal = ({
|
||||
user,
|
||||
accessToken: accessTokenProp,
|
||||
}: SupabaseContextType) => {
|
||||
const refreshTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
const [supabaseUser, setSupabaseUser] = useState<Pick<User, 'id' | 'is_anonymous'> | null>(user);
|
||||
const [accessToken, setAccessToken] = useState(accessTokenProp);
|
||||
|
||||
const isAnonymousUser: boolean = !user?.id || user?.is_anonymous === true;
|
||||
|
||||
|
@ -27,9 +32,12 @@ const useSupabaseContextInternal = ({ user }: SupabaseContextType) => {
|
|||
const user = session?.user ?? null;
|
||||
const expiresAt = session?.expires_at ?? 0;
|
||||
const timerMs = expiresAt - fiveMinutes;
|
||||
const accessToken = session?.access_token ?? '';
|
||||
|
||||
setSupabaseUser(user);
|
||||
|
||||
if (accessToken) setAccessToken(accessToken);
|
||||
|
||||
if (refreshTimerRef.current) {
|
||||
clearTimeout(refreshTimerRef.current);
|
||||
}
|
||||
|
@ -48,7 +56,9 @@ const useSupabaseContextInternal = ({ user }: SupabaseContextType) => {
|
|||
});
|
||||
|
||||
return {
|
||||
supabaseUser,
|
||||
isAnonymousUser,
|
||||
accessToken,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -58,8 +68,8 @@ const SupabaseContext = createContext<ReturnType<typeof useSupabaseContextIntern
|
|||
|
||||
export const SupabaseContextProvider: React.FC<
|
||||
SupabaseContextType & { children: React.ReactNode }
|
||||
> = React.memo(({ user, children }) => {
|
||||
const value = useSupabaseContextInternal({ user });
|
||||
> = React.memo(({ user, accessToken, children }) => {
|
||||
const value = useSupabaseContextInternal({ user, accessToken });
|
||||
|
||||
return <SupabaseContext.Provider value={value}>{children}</SupabaseContext.Provider>;
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ function transformToAuthUserDTO(user: User): AuthUserDTO {
|
|||
}
|
||||
|
||||
export const getSupabaseUser = createServerFn({ method: 'GET' }).handler(async () => {
|
||||
const supabase = await getSupabaseServerClient();
|
||||
const supabase = getSupabaseServerClient();
|
||||
const { data: userData } = await supabase.auth.getUser();
|
||||
|
||||
if (!userData.user) {
|
||||
|
@ -43,7 +43,7 @@ export const getSupabaseUser = createServerFn({ method: 'GET' }).handler(async (
|
|||
created_at: anon.data.user?.created_at ?? '',
|
||||
} satisfies AuthUserDTO,
|
||||
accessToken: anon.data.accessToken,
|
||||
} as { user: AuthUserDTO; accessToken: string | undefined };
|
||||
} as { user: AuthUserDTO; accessToken: string };
|
||||
}
|
||||
|
||||
// Get the session first
|
||||
|
@ -56,5 +56,5 @@ export const getSupabaseUser = createServerFn({ method: 'GET' }).handler(async (
|
|||
return {
|
||||
user,
|
||||
accessToken,
|
||||
} as { user: AuthUserDTO; accessToken: string | undefined };
|
||||
} as { user: AuthUserDTO; accessToken: string };
|
||||
});
|
||||
|
|
|
@ -33,7 +33,7 @@ export const Route = createRootRouteWithContext<AppRouterContext>()({
|
|||
});
|
||||
|
||||
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
const { user } = Route.useRouteContext();
|
||||
const { user, accessToken } = Route.useRouteContext();
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
|
@ -41,7 +41,9 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
|||
<HeadContent />
|
||||
</head>
|
||||
<body>
|
||||
<RootProviders user={user}>{children}</RootProviders>
|
||||
<RootProviders user={user} accessToken={accessToken}>
|
||||
{children}
|
||||
</RootProviders>
|
||||
<TanstackDevtools />
|
||||
<Scripts />
|
||||
</body>
|
||||
|
|
Loading…
Reference in New Issue