diff --git a/apps/expo/app/auth/(login)/reset-password.tsx b/apps/expo/app/auth/(login)/reset-password.tsx index 0f2e84bc98..c2d8ad7874 100644 --- a/apps/expo/app/auth/(login)/reset-password.tsx +++ b/apps/expo/app/auth/(login)/reset-password.tsx @@ -131,7 +131,7 @@ export default function ResetPasswordScreen() { setIsLoading(true); // Call the API to reset the password - await resetPassword(params.email, params.code, value.password); + await resetPassword(params.email, { code: params.code, newPassword: value.password }); // Show success message and navigate to login Alert.alert(t('common.success'), t('auth.resetPasswordSuccess'), [ diff --git a/apps/expo/features/auth/hooks/useAuthActions.ts b/apps/expo/features/auth/hooks/useAuthActions.ts index 18f1764d0d..c529ff13e1 100644 --- a/apps/expo/features/auth/hooks/useAuthActions.ts +++ b/apps/expo/features/auth/hooks/useAuthActions.ts @@ -280,7 +280,8 @@ export function useAuthActions() { } }; - const resetPassword = async (email: string, code: string, newPassword: string) => { + const resetPassword = async (email: string, opts: { code: string; newPassword: string }) => { + const { code, newPassword } = opts; try { const response = await fetch(`${clientEnvs.EXPO_PUBLIC_API_URL}/api/auth/reset-password`, { method: 'POST', diff --git a/apps/expo/features/catalog/hooks/useSimilarItems.ts b/apps/expo/features/catalog/hooks/useSimilarItems.ts index 28a5fb61d5..dd73029b9c 100644 --- a/apps/expo/features/catalog/hooks/useSimilarItems.ts +++ b/apps/expo/features/catalog/hooks/useSimilarItems.ts @@ -42,9 +42,9 @@ export const getSimilarCatalogItems = async ( // API function for pack item similar items export const getSimilarPackItems = async ( packId: string, - itemId: string, - params?: SimilarItemsParams, + opts: { itemId: string; params?: SimilarItemsParams }, ): Promise<{ items: SimilarItem[]; total: number; sourceItem: PackItem }> => { + const { itemId, params } = opts; try { const queryParams = new URLSearchParams(); if (params?.limit) queryParams.append('limit', params.limit.toString()); @@ -72,12 +72,16 @@ export function useSimilarCatalogItems(id: string, params?: SimilarItemsParams) } // Hook for pack item similar items -export function useSimilarPackItems(packId: string, itemId: string, params?: SimilarItemsParams) { +export function useSimilarPackItems( + packId: string, + opts: { itemId: string; params?: SimilarItemsParams }, +) { + const { itemId, params } = opts; const { isQueryEnabledWithAccessToken } = useAuthenticatedQueryToolkit(); return useQuery({ queryKey: ['similarPackItems', packId, itemId, params], - queryFn: () => getSimilarPackItems(packId, itemId, params), + queryFn: () => getSimilarPackItems(packId, { itemId, params }), enabled: isQueryEnabledWithAccessToken && !!packId && !!itemId, staleTime: 5 * 60 * 1000, // 5 minutes }); diff --git a/apps/expo/features/packs/components/GapSuggestion.tsx b/apps/expo/features/packs/components/GapSuggestion.tsx index c3919de9f8..918b8b4684 100644 --- a/apps/expo/features/packs/components/GapSuggestion.tsx +++ b/apps/expo/features/packs/components/GapSuggestion.tsx @@ -27,9 +27,12 @@ export function GapSuggestion({ gap, packId }: GapSuggestionProps) { }); const handleAddItem = async (item: CatalogItem) => { - await addItemToPack(packId, item, { - consumable: gap.consumable, - worn: gap.worn, + await addItemToPack(packId, { + catalogItem: item, + data: { + consumable: gap.consumable, + worn: gap.worn, + }, }); setCatalogSuggestionsModalVisible(false); setIsAddressed(true); diff --git a/apps/expo/features/packs/components/SimilarItemsForPackItem.tsx b/apps/expo/features/packs/components/SimilarItemsForPackItem.tsx index c1b6ee9f72..3ed40e1bdd 100644 --- a/apps/expo/features/packs/components/SimilarItemsForPackItem.tsx +++ b/apps/expo/features/packs/components/SimilarItemsForPackItem.tsx @@ -81,9 +81,9 @@ export const SimilarItemsForPackItem: React.FC = ( }) => { const { t } = useTranslation(); const router = useRouter(); - const { data, isLoading, isError } = useSimilarPackItems(packId, itemId, { - limit, - threshold, + const { data, isLoading, isError } = useSimilarPackItems(packId, { + itemId, + params: { limit, threshold }, }); const handleItemPress = (catalogItemId: string) => { diff --git a/apps/expo/features/packs/hooks/useAddCatalogItem.ts b/apps/expo/features/packs/hooks/useAddCatalogItem.ts index ce394d492f..9bbe400bd9 100644 --- a/apps/expo/features/packs/hooks/useAddCatalogItem.ts +++ b/apps/expo/features/packs/hooks/useAddCatalogItem.ts @@ -11,9 +11,9 @@ export function useAddCatalogItem() { const addItemToPack = async ( packId: string, - catalogItem: CatalogItem, - data?: Partial, + opts: { catalogItem: CatalogItem; data?: Partial }, ) => { + const { catalogItem, data } = opts; setIsLoading(true); const cachedImageFilename = await cacheCatalogItemImage(catalogItem.images?.[0]); diff --git a/apps/guides/components/ui/chart.tsx b/apps/guides/components/ui/chart.tsx index 1084b371c8..8c6d88ece2 100644 --- a/apps/guides/components/ui/chart.tsx +++ b/apps/guides/components/ui/chart.tsx @@ -134,7 +134,7 @@ const ChartTooltipContent = React.forwardRef< const [item] = payload; assertDefined(item); const key = `${labelKey || item.dataKey || item.name || 'value'}`; - const itemConfig = getPayloadConfigFromPayload(config, item, key); + const itemConfig = getPayloadConfigFromPayload(config, { payload: item, key }); const value = !labelKey && typeof label === 'string' ? config[label as keyof typeof config]?.label || label @@ -171,7 +171,7 @@ const ChartTooltipContent = React.forwardRef<
{payload.map((item, index) => { const key = `${nameKey || item.name || item.dataKey || 'value'}`; - const itemConfig = getPayloadConfigFromPayload(config, item, key); + const itemConfig = getPayloadConfigFromPayload(config, { payload: item, key }); const indicatorColor = color || item.payload.fill || item.color; return ( @@ -267,7 +267,7 @@ const ChartLegendContent = React.forwardRef< > {payload.map((item) => { const key = `${nameKey || item.dataKey || 'value'}`; - const itemConfig = getPayloadConfigFromPayload(config, item, key); + const itemConfig = getPayloadConfigFromPayload(config, { payload: item, key }); return (
, - enhancedContent: string, + opts: { metadata: Record; enhancedContent: string }, ): void { + const { metadata, enhancedContent } = opts; const newFileContent = matter.stringify(enhancedContent, metadata); fs.writeFileSync(filePath, newFileContent, 'utf8'); } @@ -153,13 +153,13 @@ function writeEnhancedContent( async function enhanceFile( filePath: string, - options: CliOptions, - enhancementOptions: ContentEnhancementOptions, + opts: { cliOptions: CliOptions; enhancementOptions: ContentEnhancementOptions }, ): Promise<{ enhanced: boolean; productsAdded: number; error?: string; }> { + const { cliOptions: options, enhancementOptions } = opts; try { const fileName = path.basename(filePath); @@ -212,7 +212,7 @@ async function enhanceFile( } // Write enhanced content - writeEnhancedContent(filePath, metadata, result.content); + writeEnhancedContent(filePath, { metadata, enhancedContent: result.content }); console.log( chalk.green(` ✅ Enhanced ${fileName} with ${result.productsUsed.length} product links`), @@ -282,7 +282,7 @@ async function enhanceContent(cliOptions: CliOptions): Promise { for (const filePath of filesToProcess) { stats.processed++; - const result = await enhanceFile(filePath, cliOptions, enhancementOptions); + const result = await enhanceFile(filePath, { cliOptions, enhancementOptions }); if (result.error) { stats.errors++; diff --git a/apps/guides/scripts/generate-content.ts b/apps/guides/scripts/generate-content.ts index eee0a2a560..8fc3823edd 100644 --- a/apps/guides/scripts/generate-content.ts +++ b/apps/guides/scripts/generate-content.ts @@ -165,9 +165,9 @@ function getExistingContent(): ContentMetadata[] { // Generate topic ideas based on categories and existing content async function generateTopicIdeas( count: number, - categories?: ContentCategory[], - existingContent: ContentMetadata[] = [], + opts: { categories?: ContentCategory[]; existingContent?: ContentMetadata[] } = {}, ): Promise { + const { categories, existingContent = [] } = opts; console.log(chalk.blue(`Generating ${count} topic ideas...`)); const categoryPrompt = @@ -421,7 +421,7 @@ async function generatePosts(count: number, categories?: ContentCategory[]): Pro console.log(chalk.blue(`Found ${existingContent.length} existing articles`)); // Generate topic ideas with awareness of existing content - const topics = await generateTopicIdeas(count, categories, existingContent); + const topics = await generateTopicIdeas(count, { categories, existingContent }); console.log(chalk.green(`✓ Generated ${topics.length} topic ideas`)); // Generate content for each topic diff --git a/packages/analytics/src/core/query-builder.ts b/packages/analytics/src/core/query-builder.ts index 24607cadac..73538c9928 100644 --- a/packages/analytics/src/core/query-builder.ts +++ b/packages/analytics/src/core/query-builder.ts @@ -400,7 +400,8 @@ export class QueryBuilder { `; } - trendsQuery(keyword: string, sites?: string[], days = 90): string { + trendsQuery(keyword: string, opts: { sites?: string[]; days?: number } = {}): string { + const { sites, days = 90 } = opts; const source = SQLFragments.readCsvSource(this.bucketPath); const kw = SQLFragments.escapeSql(keyword.toLowerCase()); diff --git a/packages/analytics/test/core/query-builder.test.ts b/packages/analytics/test/core/query-builder.test.ts index e1b30122d7..96bca2b44e 100644 --- a/packages/analytics/test/core/query-builder.test.ts +++ b/packages/analytics/test/core/query-builder.test.ts @@ -247,7 +247,7 @@ describe('QueryBuilder', () => { }); it('uses custom days parameter', () => { - const sql = qb.trendsQuery('tent', undefined, 30); + const sql = qb.trendsQuery('tent', { days: 30 }); expect(sql).toContain("INTERVAL '30 days'"); }); }); diff --git a/packages/api/container_src/server.ts b/packages/api/container_src/server.ts index 387bf2b954..4929486ae5 100644 --- a/packages/api/container_src/server.ts +++ b/packages/api/container_src/server.ts @@ -68,12 +68,12 @@ const TikTokImportSchema = z.object({ */ function detectMediaTypeAndExtension( response: Response, - buffer?: ArrayBuffer, - isVideo = false, + opts: { buffer?: ArrayBuffer; isVideo?: boolean } = {}, ): { contentType: string; extension: string; } { + const { buffer, isVideo = false } = opts; // Try to get content type from headers first const headerContentType = response.headers.get('content-type'); @@ -169,9 +169,9 @@ function detectMediaTypeAndExtension( */ async function downloadAndRehostImage( imageUrl: string, - contentId: string, - index: number, + opts: { contentId: string; index: number }, ): Promise { + const { contentId, index } = opts; if (!s3Client || !env) { console.warn('R2 client not available, skipping image rehosting'); return null; @@ -202,7 +202,9 @@ async function downloadAndRehostImage( const imageBuffer = await response.arrayBuffer(); // Detect the actual image type and extension - const { contentType, extension } = detectMediaTypeAndExtension(response, imageBuffer, false); + const { contentType, extension } = detectMediaTypeAndExtension(response, { + buffer: imageBuffer, + }); const timestamp = Date.now(); const imageKey = `tiktok-temp/${contentId}/${timestamp}-${index}.${extension}`; @@ -264,7 +266,7 @@ async function uploadVideoToGoogle(videoUrl: string): Promise { console.log(`Video uploaded to Google AI. File URI: ${myfile.uri}, name: ${myfile.name}`); // Wait for ACTIVE state if (!myfile.name) throw new Error('Google AI upload did not return a file name'); - await waitForFileToBeActiveGoogle(googleAi, myfile.name); + await waitForFileToBeActiveGoogle(googleAi, { fileName: myfile.name }); return myfile.uri || null; } catch (error) { console.error('Failed to upload video to Google:', error); @@ -277,9 +279,9 @@ async function uploadVideoToGoogle(videoUrl: string): Promise { */ async function waitForFileToBeActiveGoogle( ai: GoogleGenAI, - fileName: string, - maxWaitTimeMs: number = 300000, + opts: { fileName: string; maxWaitTimeMs?: number }, ): Promise { + const { fileName, maxWaitTimeMs = 300000 } = opts; const startTime = Date.now(); while (Date.now() - startTime < maxWaitTimeMs) { const fileInfo = await ai.files.get({ name: fileName }); @@ -322,7 +324,7 @@ async function downloadAndRehostImages( // Process all images in parallel with best effort approach const results = await Promise.allSettled( - imageUrls.map((url, index) => downloadAndRehostImage(url, contentId, index)), + imageUrls.map((url, index) => downloadAndRehostImage(url, { contentId, index })), ); const rehostedUrls: string[] = []; diff --git a/packages/api/src/routes/catalog/vectorSearchRoute.ts b/packages/api/src/routes/catalog/vectorSearchRoute.ts index 8cfe487b93..da1e6c89bd 100644 --- a/packages/api/src/routes/catalog/vectorSearchRoute.ts +++ b/packages/api/src/routes/catalog/vectorSearchRoute.ts @@ -50,7 +50,7 @@ export const handler: RouteHandler = async (c) => { const { q: query, limit = 10, offset = 0 } = c.req.valid('query'); const catalogService = new CatalogService(c); - const result = await catalogService.vectorSearch(query, limit, offset); + const result = await catalogService.vectorSearch(query, { limit, offset }); return c.json(result, 200); } catch (error) { diff --git a/packages/api/src/routes/packs/analyzeImage.ts b/packages/api/src/routes/packs/analyzeImage.ts index 7973e39987..e00d369bf4 100644 --- a/packages/api/src/routes/packs/analyzeImage.ts +++ b/packages/api/src/routes/packs/analyzeImage.ts @@ -89,8 +89,9 @@ analyzeImageRoutes.openapi(analyzeImageRoute, async (c) => { Bucket: PACKRAT_BUCKET_R2_BUCKET_NAME, Key: image, }); - const imageUrl = await getPresignedUrl(c, command, { - expiresIn: 3600, + const imageUrl = await getPresignedUrl(c, { + command, + signOptions: { expiresIn: 3600 }, }); const imageDetectionService = new ImageDetectionService(c); diff --git a/packages/api/src/routes/upload.ts b/packages/api/src/routes/upload.ts index b453eefa97..db2cd56459 100644 --- a/packages/api/src/routes/upload.ts +++ b/packages/api/src/routes/upload.ts @@ -114,8 +114,9 @@ uploadRoutes.openapi(presignedRoute, async (c) => { }); // Generate the presigned URL - const presignedUrl = await getPresignedUrl(c, command, { - expiresIn: 3600, + const presignedUrl = await getPresignedUrl(c, { + command, + signOptions: { expiresIn: 3600 }, }); return c.json( diff --git a/packages/api/src/services/__tests__/catalogService.test.ts b/packages/api/src/services/__tests__/catalogService.test.ts index 8937273716..171f83f8f2 100644 --- a/packages/api/src/services/__tests__/catalogService.test.ts +++ b/packages/api/src/services/__tests__/catalogService.test.ts @@ -93,7 +93,7 @@ describe('CatalogService', () => { }); it('returns empty result for empty query string', async () => { - const result = await service.vectorSearch('', 10, 0); + const result = await service.vectorSearch('', { limit: 10, offset: 0 }); expect(result).toEqual({ items: [], @@ -106,7 +106,7 @@ describe('CatalogService', () => { }); it('returns empty result for whitespace-only query', async () => { - const result = await service.vectorSearch(' ', 10, 0); + const result = await service.vectorSearch(' ', { limit: 10, offset: 0 }); expect(result).toEqual({ items: [], @@ -121,7 +121,7 @@ describe('CatalogService', () => { it('returns empty result when embedding generation fails', async () => { vi.mocked(embeddingService.generateEmbedding).mockResolvedValueOnce(null); - const result = await service.vectorSearch('tent', 10, 0); + const result = await service.vectorSearch('tent', { limit: 10, offset: 0 }); expect(result).toEqual({ items: [], @@ -148,7 +148,7 @@ describe('CatalogService', () => { // We can't fully test the DB query without a real/mocked database, // but we can verify the embedding generation was called correctly try { - await service.vectorSearch('lightweight tent', 10, 0); + await service.vectorSearch('lightweight tent', { limit: 10, offset: 0 }); } catch (err) { // DB query will fail since we don't have a proper mock, but that's OK // We're just testing the input validation and embedding call diff --git a/packages/api/src/services/catalogService.ts b/packages/api/src/services/catalogService.ts index 6273dd7139..ce4ac583b1 100644 --- a/packages/api/src/services/catalogService.ts +++ b/packages/api/src/services/catalogService.ts @@ -186,8 +186,7 @@ export class CatalogService { async vectorSearch( q: string, - limit: number = 10, - offset: number = 0, + opts: { limit?: number; offset?: number } = {}, ): Promise<{ items: (Omit & { similarity: number })[]; total: number; @@ -195,6 +194,7 @@ export class CatalogService { offset: number; nextOffset: number; }> { + const { limit = 10, offset = 0 } = opts; if (!q || q.trim() === '') { return { items: [], diff --git a/packages/api/src/utils/ai/logging.ts b/packages/api/src/utils/ai/logging.ts index c1881f7dd4..a93e57b87d 100644 --- a/packages/api/src/utils/ai/logging.ts +++ b/packages/api/src/utils/ai/logging.ts @@ -13,9 +13,9 @@ export interface AIRequestLog { export function logAIRequest( env: Env, - headers: Headers, - options: Partial, + opts: { headers: Headers; log: Partial }, ): AIRequestLog { + const { headers, log: options } = opts; const log: AIRequestLog = { provider: env.AI_PROVIDER || 'openai', model: options.model || 'gpt-4o', diff --git a/packages/api/src/utils/ai/tools.ts b/packages/api/src/utils/ai/tools.ts index 9d2035ce56..95b581d76f 100644 --- a/packages/api/src/utils/ai/tools.ts +++ b/packages/api/src/utils/ai/tools.ts @@ -175,7 +175,10 @@ export function createTools(c: Context, userId: number) { }), execute: async ({ query, limit, offset }) => { try { - const data = await catalogService.vectorSearch(query, limit || 10, offset || 0); + const data = await catalogService.vectorSearch(query, { + limit: limit || 10, + offset: offset || 0, + }); return { success: true, data, diff --git a/packages/api/src/utils/getPresignedUrl.ts b/packages/api/src/utils/getPresignedUrl.ts index faac52f437..696f869acb 100644 --- a/packages/api/src/utils/getPresignedUrl.ts +++ b/packages/api/src/utils/getPresignedUrl.ts @@ -5,9 +5,12 @@ import { getEnv } from './env-validation'; export async function getPresignedUrl( c: Context, - command: GetObjectCommand | PutObjectCommand, - options: Parameters[2], + opts: { + command: GetObjectCommand | PutObjectCommand; + signOptions: Parameters[2]; + }, ): Promise { + const { command, signOptions } = opts; const { R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, CLOUDFLARE_ACCOUNT_ID } = getEnv(c); // Initialize S3 client for R2 @@ -21,7 +24,7 @@ export async function getPresignedUrl( }); // Using S3Client because R2 binding doesn't seem to support presigned URLs directly // Generate the presigned URL - const presignedUrl = await getSignedUrl(s3Client, command, options); + const presignedUrl = await getSignedUrl(s3Client, command, signOptions); return presignedUrl; } diff --git a/packages/api/test/auth.test.ts b/packages/api/test/auth.test.ts index ae933509b1..9d9cd3bfbe 100644 --- a/packages/api/test/auth.test.ts +++ b/packages/api/test/auth.test.ts @@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import app from '../src/index'; import { apiWithAuth, + apiWithAuthAs, expectBadRequest, expectUnauthorized, httpMethods, @@ -21,7 +22,7 @@ describe('Auth Routes', () => { describe('POST /auth/login', () => { it('requires email and password', async () => { - const res = await authApi('/login', httpMethods.post('', {})); + const res = await authApi('/login', httpMethods.post({})); expectBadRequest(res); const data = await res.json(); @@ -29,19 +30,19 @@ describe('Auth Routes', () => { }); it('requires email field', async () => { - const res = await authApi('/login', httpMethods.post('', { password: 'test123' })); + const res = await authApi('/login', httpMethods.post({ password: 'test123' })); expectBadRequest(res); }); it('requires password field', async () => { - const res = await authApi('/login', httpMethods.post('', { email: 'test@example.com' })); + const res = await authApi('/login', httpMethods.post({ email: 'test@example.com' })); expectBadRequest(res); }); it('returns error for non-existent user', async () => { const res = await authApi( '/login', - httpMethods.post('', { + httpMethods.post({ email: 'nonexistent@example.com', password: 'password123', }), @@ -56,7 +57,7 @@ describe('Auth Routes', () => { const user = await createTestUser({ email: 'login-test@example.com' }); const res = await authApi( '/login', - httpMethods.post('', { + httpMethods.post({ email: user.email, password: 'wrong-password', }), @@ -70,7 +71,7 @@ describe('Auth Routes', () => { const user = await createTestUser({ email: 'login-success@example.com' }); const res = await authApi( '/login', - httpMethods.post('', { + httpMethods.post({ email: user.email, password: user.password, }), @@ -91,7 +92,7 @@ describe('Auth Routes', () => { }); const res = await authApi( '/login', - httpMethods.post('', { + httpMethods.post({ email: user.email, password: user.password, }), @@ -104,7 +105,7 @@ describe('Auth Routes', () => { describe('POST /auth/register', () => { it('requires email and password', async () => { - const res = await authApi('/register', httpMethods.post('', {})); + const res = await authApi('/register', httpMethods.post({})); expectBadRequest(res); const data = await res.json(); @@ -114,7 +115,7 @@ describe('Auth Routes', () => { it('validates email format', async () => { const res = await authApi( '/register', - httpMethods.post('', { + httpMethods.post({ email: 'invalid-email', password: 'Password123!', }), @@ -128,7 +129,7 @@ describe('Auth Routes', () => { it('validates password strength', async () => { const res = await authApi( '/register', - httpMethods.post('', { + httpMethods.post({ email: 'test@example.com', password: '123', // Too weak }), @@ -142,7 +143,7 @@ describe('Auth Routes', () => { it('accepts valid registration data', async () => { const res = await authApi( '/register', - httpMethods.post('', { + httpMethods.post({ email: 'newuser@example.com', password: 'Password123!', firstName: 'Test', @@ -163,7 +164,7 @@ describe('Auth Routes', () => { await createTestUser({ email: 'existing@example.com' }); const res = await authApi( '/register', - httpMethods.post('', { + httpMethods.post({ email: 'existing@example.com', password: 'Password123!', }), @@ -176,7 +177,7 @@ describe('Auth Routes', () => { describe('POST /auth/verify-email', () => { it('requires email and code', async () => { - const res = await authApi('/verify-email', httpMethods.post('', {})); + const res = await authApi('/verify-email', httpMethods.post({})); expectBadRequest(res); const data = await res.json(); @@ -184,29 +185,26 @@ describe('Auth Routes', () => { }); it('requires email field', async () => { - const res = await authApi('/verify-email', httpMethods.post('', { code: '12345' })); + const res = await authApi('/verify-email', httpMethods.post({ code: '12345' })); expectBadRequest(res); }); it('requires code field', async () => { - const res = await authApi( - '/verify-email', - httpMethods.post('', { email: 'test@example.com' }), - ); + const res = await authApi('/verify-email', httpMethods.post({ email: 'test@example.com' })); expectBadRequest(res); }); }); describe('POST /auth/resend-verification', () => { it('requires email', async () => { - const res = await authApi('/resend-verification', httpMethods.post('', {})); + const res = await authApi('/resend-verification', httpMethods.post({})); expectBadRequest(res); }); it('validates email format', async () => { const res = await authApi( '/resend-verification', - httpMethods.post('', { + httpMethods.post({ email: 'invalid-email', }), ); @@ -216,14 +214,14 @@ describe('Auth Routes', () => { describe('POST /auth/forgot-password', () => { it('requires email', async () => { - const res = await authApi('/forgot-password', httpMethods.post('', {})); + const res = await authApi('/forgot-password', httpMethods.post({})); expectBadRequest(res); }); it('validates email format', async () => { const res = await authApi( '/forgot-password', - httpMethods.post('', { + httpMethods.post({ email: 'invalid-email', }), ); @@ -233,7 +231,7 @@ describe('Auth Routes', () => { describe('POST /auth/reset-password', () => { it('requires email, code, and new password', async () => { - const res = await authApi('/reset-password', httpMethods.post('', {})); + const res = await authApi('/reset-password', httpMethods.post({})); expect(res.status).toBe(400); const data = await res.json(); @@ -243,7 +241,7 @@ describe('Auth Routes', () => { it('validates new password strength', async () => { const res = await authApi( '/reset-password', - httpMethods.post('', { + httpMethods.post({ email: 'test@example.com', code: '12345', newPassword: '123', // Too weak @@ -261,7 +259,7 @@ describe('Auth Routes', () => { it('returns user data when authenticated', async () => { const testUser = await createTestUser(); - const res = await apiWithAuth('/auth/me', undefined, testUser as typeof TEST_USER); + const res = await apiWithAuthAs('/auth/me', { user: testUser as typeof TEST_USER }); expect(res.status).toBe(200); const data = await res.json(); expect(data.success).toBe(true); @@ -272,14 +270,14 @@ describe('Auth Routes', () => { describe('POST /auth/refresh', () => { it('requires refresh token', async () => { - const res = await authApi('/refresh', httpMethods.post('', {})); + const res = await authApi('/refresh', httpMethods.post({})); expectBadRequest(res); }); }); describe('DELETE /auth/', () => { it('requires authentication', async () => { - const res = await authApi('', httpMethods.delete('')); + const res = await authApi('', httpMethods.delete()); expectUnauthorized(res); }); @@ -299,14 +297,14 @@ describe('Auth Routes', () => { describe('POST /auth/apple', () => { it('requires identity token and authorization code', async () => { - const res = await authApi('/apple', httpMethods.post('', {})); + const res = await authApi('/apple', httpMethods.post({})); expectBadRequest(res); }); it('validates identity token format', async () => { const res = await authApi( '/apple', - httpMethods.post('', { + httpMethods.post({ identityToken: 'invalid-token', authorizationCode: 'auth-code', }), @@ -317,7 +315,7 @@ describe('Auth Routes', () => { it('handles invalid apple token', async () => { const res = await authApi( '/apple', - httpMethods.post('', { + httpMethods.post({ identityToken: 'invalid-token', }), ); @@ -327,7 +325,7 @@ describe('Auth Routes', () => { describe('POST /auth/google', () => { it('requires ID token', async () => { - const res = await authApi('/google', httpMethods.post('', {})); + const res = await authApi('/google', httpMethods.post({})); expect(res.status).toBe(400); const data = await res.json(); @@ -337,7 +335,7 @@ describe('Auth Routes', () => { it('validates Google ID token and returns user', async () => { const res = await authApi( '/google', - httpMethods.post('', { + httpMethods.post({ idToken: 'mock-google-token', }), ); @@ -354,7 +352,7 @@ describe('Auth Routes', () => { // First login creates the user await authApi( '/google', - httpMethods.post('', { + httpMethods.post({ idToken: 'mock-google-token', }), ); @@ -362,7 +360,7 @@ describe('Auth Routes', () => { // Second login should find the existing user const res = await authApi( '/google', - httpMethods.post('', { + httpMethods.post({ idToken: 'mock-google-token', }), ); diff --git a/packages/api/test/catalog.test.ts b/packages/api/test/catalog.test.ts index ca35abf417..c92b22d9fb 100644 --- a/packages/api/test/catalog.test.ts +++ b/packages/api/test/catalog.test.ts @@ -14,27 +14,27 @@ import { describe('Catalog Routes', () => { describe('Authentication', () => { it('GET /catalog/ requires auth', async () => { - const res = await api('/catalog', httpMethods.get('')); + const res = await api('/catalog', httpMethods.get()); expectUnauthorized(res); }); it('GET /catalog/:id requires auth', async () => { - const res = await api('/catalog/1', httpMethods.get('')); + const res = await api('/catalog/1', httpMethods.get()); expectUnauthorized(res); }); it('POST /catalog/ requires auth', async () => { - const res = await api('/catalog', httpMethods.post('', {})); + const res = await api('/catalog', httpMethods.post({})); expectUnauthorized(res); }); it('PUT /catalog/:id requires auth', async () => { - const res = await api('/catalog/1', httpMethods.put('', {})); + const res = await api('/catalog/1', httpMethods.put({})); expectUnauthorized(res); }); it('DELETE /catalog/:id requires auth', async () => { - const res = await api('/catalog/1', httpMethods.delete('')); + const res = await api('/catalog/1', httpMethods.delete()); expectUnauthorized(res); }); }); @@ -132,7 +132,7 @@ describe('Catalog Routes', () => { price: 299.99, }; - const res = await apiWithAuth('/catalog', httpMethods.post('', newItem)); + const res = await apiWithAuth('/catalog', httpMethods.post(newItem)); expect([201, 200]).toContain(res.status); const data = await expectJsonResponse(res, ['id']); @@ -140,14 +140,14 @@ describe('Catalog Routes', () => { }); it('validates required fields', async () => { - const res = await apiWithAuth('/catalog', httpMethods.post('', {})); + const res = await apiWithAuth('/catalog', httpMethods.post({})); expectBadRequest(res); }); it('validates name field', async () => { const res = await apiWithAuth( '/catalog', - httpMethods.post('', { + httpMethods.post({ productUrl: 'https://example.com/tent', sku: 'TEST-123', weight: 1200, @@ -160,7 +160,7 @@ describe('Catalog Routes', () => { it('validates weight field', async () => { const res = await apiWithAuth( '/catalog', - httpMethods.post('', { + httpMethods.post({ name: 'Test Item', productUrl: 'https://example.com/tent', sku: 'TEST-123', @@ -182,7 +182,7 @@ describe('Catalog Routes', () => { weight: 1500, }; - const res = await apiWithAuth(`/catalog/${seededItem.id}`, httpMethods.put('', updateData)); + const res = await apiWithAuth(`/catalog/${seededItem.id}`, httpMethods.put(updateData)); expect(res.status).toBe(200); const data = await expectJsonResponse(res); @@ -192,7 +192,7 @@ describe('Catalog Routes', () => { it('returns 404 for non-existent item', async () => { const res = await apiWithAuth( '/catalog/999999', - httpMethods.put('', { + httpMethods.put({ name: 'Updated Item', }), ); @@ -205,7 +205,7 @@ describe('Catalog Routes', () => { const res = await apiWithAuth( `/catalog/${seededItem.id}`, - httpMethods.put('', { + httpMethods.put({ weight: -1, // Invalid weight }), ); @@ -218,13 +218,13 @@ describe('Catalog Routes', () => { // Seed a catalog item first const seededItem = await seedCatalogItem({ name: 'Item to Delete' }); - const res = await apiWithAuth(`/catalog/${seededItem.id}`, httpMethods.delete('')); + const res = await apiWithAuth(`/catalog/${seededItem.id}`, httpMethods.delete()); expect(res.status).toBeOneOf([200, 204]); }); it('returns 404 for non-existent item', async () => { - const res = await apiWithAuth('/catalog/999999', httpMethods.delete('')); + const res = await apiWithAuth('/catalog/999999', httpMethods.delete()); expectNotFound(res); }); }); @@ -233,7 +233,7 @@ describe('Catalog Routes', () => { it('queues ETL job', async () => { const res = await apiWithApiKey( '/catalog/etl', - httpMethods.post('', { + httpMethods.post({ filename: 'test.csv', chunks: ['chunk1.csv'], source: 'test-source', @@ -248,7 +248,7 @@ describe('Catalog Routes', () => { it('regular users cannot queue ETL without API key', async () => { const res = await apiWithAuth( '/catalog/etl', - httpMethods.post('', { + httpMethods.post({ filename: 'test.csv', chunks: ['chunk1.csv'], source: 'test-source', @@ -261,14 +261,14 @@ describe('Catalog Routes', () => { describe('POST /catalog/backfill-embeddings', () => { it('backfills embeddings', async () => { - const res = await apiWithApiKey('/catalog/backfill-embeddings', httpMethods.post('', {})); + const res = await apiWithApiKey('/catalog/backfill-embeddings', httpMethods.post({})); expect(res.status).toBe(200); await expectJsonResponse(res); }); it('regular users cannot backfill embeddings without API key', async () => { - const res = await apiWithAuth('/catalog/backfill-embeddings', httpMethods.post('', {})); + const res = await apiWithAuth('/catalog/backfill-embeddings', httpMethods.post({})); expect(res.status).toBe(401); }); }); diff --git a/packages/api/test/chat.test.ts b/packages/api/test/chat.test.ts index 0fe15f72ca..b18b178875 100644 --- a/packages/api/test/chat.test.ts +++ b/packages/api/test/chat.test.ts @@ -18,7 +18,7 @@ describe('Chat Routes', () => { describe('Authentication', () => { it('requires auth for chat endpoint', async () => { - const res = await api('/chat', httpMethods.post('', {})); + const res = await api('/chat', httpMethods.post({})); expectUnauthorized(res); }); }); @@ -39,7 +39,7 @@ describe('Chat Routes', () => { }, }; - const res = await apiWithAuth('/chat', httpMethods.post('', chatMessage)); + const res = await apiWithAuth('/chat', httpMethods.post(chatMessage)); expect(res.status).toBe(200); const data = await expectJsonResponse(res, ['response']); @@ -48,7 +48,7 @@ describe('Chat Routes', () => { }); it('requires message field', async () => { - const res = await apiWithAuth('/chat', httpMethods.post('', {})); + const res = await apiWithAuth('/chat', httpMethods.post({})); expectBadRequest(res); const data = await res.json(); @@ -66,7 +66,7 @@ describe('Chat Routes', () => { }, }; - const res = await apiWithAuth('/chat', httpMethods.post('', chatWithContext)); + const res = await apiWithAuth('/chat', httpMethods.post(chatWithContext)); expect(res.status).toBe(200); const data = await expectJsonResponse(res, ['response']); @@ -83,7 +83,7 @@ describe('Chat Routes', () => { }, }; - const res = await apiWithAuth('/chat', httpMethods.post('', gearRequest)); + const res = await apiWithAuth('/chat', httpMethods.post(gearRequest)); expect(res.status).toBe(200); const data = await expectJsonResponse(res); @@ -104,7 +104,7 @@ describe('Chat Routes', () => { }, }; - const res = await apiWithAuth('/chat', httpMethods.post('', packingRequest)); + const res = await apiWithAuth('/chat', httpMethods.post(packingRequest)); expect(res.status).toBe(200); const data = await expectJsonResponse(res); @@ -126,7 +126,7 @@ describe('Chat Routes', () => { }, }; - const res = await apiWithAuth('/chat', httpMethods.post('', tripRequest)); + const res = await apiWithAuth('/chat', httpMethods.post(tripRequest)); expect(res.status).toBe(200); await expectJsonResponse(res, ['response']); @@ -139,7 +139,7 @@ describe('Chat Routes', () => { conversationId: 'test-conversation-1', }; - const res1 = await apiWithAuth('/chat', httpMethods.post('', firstMessage)); + const res1 = await apiWithAuth('/chat', httpMethods.post(firstMessage)); expect(res1.status).toBe(200); const data1 = await res1.json(); @@ -150,7 +150,7 @@ describe('Chat Routes', () => { conversationId: data1.conversationId || 'test-conversation-1', }; - const res2 = await apiWithAuth('/chat', httpMethods.post('', followupMessage)); + const res2 = await apiWithAuth('/chat', httpMethods.post(followupMessage)); expect(res2.status).toBe(200); await expectJsonResponse(res2, ['response']); @@ -162,7 +162,7 @@ describe('Chat Routes', () => { // Mock AI service failure const res = await apiWithAuth( '/chat', - httpMethods.post('', { + httpMethods.post({ message: 'This might cause an AI error', }), ); @@ -179,7 +179,7 @@ describe('Chat Routes', () => { it('handles malformed requests', async () => { const res = await apiWithAuth( '/chat', - httpMethods.post('', { + httpMethods.post({ invalidField: 'invalid', }), ); @@ -190,7 +190,7 @@ describe('Chat Routes', () => { it('handles empty messages', async () => { const res = await apiWithAuth( '/chat', - httpMethods.post('', { + httpMethods.post({ message: '', }), ); @@ -203,7 +203,7 @@ describe('Chat Routes', () => { message: 'Test with special chars: @#$%^&*()[]{}|\\:";\'<>?,./', }; - const res = await apiWithAuth('/chat', httpMethods.post('', specialMessage)); + const res = await apiWithAuth('/chat', httpMethods.post(specialMessage)); // Should handle gracefully expect([200, 400]).toContain(res.status); @@ -222,7 +222,7 @@ describe('Chat Routes', () => { .map((_, i) => apiWithAuth( '/chat', - httpMethods.post('', { + httpMethods.post({ message: `Test message ${i + 1}`, }), ), diff --git a/packages/api/test/generate-from-online-content.test.ts b/packages/api/test/generate-from-online-content.test.ts index 19ab671aca..3e42292f0f 100644 --- a/packages/api/test/generate-from-online-content.test.ts +++ b/packages/api/test/generate-from-online-content.test.ts @@ -142,7 +142,7 @@ describe('Generate From Online Content Routes', () => { it('requires auth for generate-from-online-content endpoint', async () => { const res = await api( '/pack-templates/generate-from-online-content', - httpMethods.post('', { + httpMethods.post({ contentUrl: 'https://www.tiktok.com/@user/video/1234567890', }), ); @@ -154,7 +154,7 @@ describe('Generate From Online Content Routes', () => { it('returns 403 for non-admin users', async () => { const res = await apiWithAuth( '/pack-templates/generate-from-online-content', - httpMethods.post('', { + httpMethods.post({ contentUrl: 'https://www.tiktok.com/@user/video/1234567890', }), ); @@ -169,7 +169,7 @@ describe('Generate From Online Content Routes', () => { it('requires contentUrl field', async () => { const res = await apiWithAdmin( '/pack-templates/generate-from-online-content', - httpMethods.post('', {}), + httpMethods.post({}), ); expectBadRequest(res); }); @@ -177,7 +177,7 @@ describe('Generate From Online Content Routes', () => { it('requires valid URL format', async () => { const res = await apiWithAdmin( '/pack-templates/generate-from-online-content', - httpMethods.post('', { + httpMethods.post({ contentUrl: 'invalid-url', }), ); @@ -201,7 +201,7 @@ describe('Generate From Online Content Routes', () => { const res = await apiWithAdmin( '/pack-templates/generate-from-online-content', - httpMethods.post('', { + httpMethods.post({ contentUrl: 'https://www.tiktok.com/@user/video/1234567890', }), ); @@ -218,7 +218,7 @@ describe('Generate From Online Content Routes', () => { it('successfully generates template from online content URL', async () => { const res = await apiWithAdmin( '/pack-templates/generate-from-online-content', - httpMethods.post('', { + httpMethods.post({ contentUrl: 'https://www.tiktok.com/@user/video/9999999999', }), ); @@ -246,7 +246,7 @@ describe('Generate From Online Content Routes', () => { it('accepts isAppTemplate flag', async () => { const res = await apiWithAdmin( '/pack-templates/generate-from-online-content', - httpMethods.post('', { + httpMethods.post({ contentUrl: 'https://www.tiktok.com/@user/video/temp-test', isAppTemplate: false, }), @@ -260,7 +260,7 @@ describe('Generate From Online Content Routes', () => { it('returns items with matched catalog data when available', async () => { const res = await apiWithAdmin( '/pack-templates/generate-from-online-content', - httpMethods.post('', { + httpMethods.post({ contentUrl: 'https://www.tiktok.com/@user/video/catalog-test', }), ); diff --git a/packages/api/test/guides.test.ts b/packages/api/test/guides.test.ts index 536fcc1166..46a952ae8a 100644 --- a/packages/api/test/guides.test.ts +++ b/packages/api/test/guides.test.ts @@ -168,22 +168,22 @@ vi.mock('@packrat/api/services/r2-bucket', () => { describe('Guides Routes', () => { describe('Authentication', () => { it('GET /guides requires auth', async () => { - const res = await api('/guides', httpMethods.get('')); + const res = await api('/guides', httpMethods.get()); expectUnauthorized(res); }); it('GET /guides/categories requires auth', async () => { - const res = await api('/guides/categories', httpMethods.get('')); + const res = await api('/guides/categories', httpMethods.get()); expectUnauthorized(res); }); it('GET /guides/search requires auth', async () => { - const res = await api('/guides/search', httpMethods.get('')); + const res = await api('/guides/search', httpMethods.get()); expectUnauthorized(res); }); it('GET /guides/:id requires auth', async () => { - const res = await api('/guides/1', httpMethods.get('')); + const res = await api('/guides/1', httpMethods.get()); expectUnauthorized(res); }); }); diff --git a/packages/api/test/image-detection.test.ts b/packages/api/test/image-detection.test.ts index 97a7e00141..167f6ab521 100644 --- a/packages/api/test/image-detection.test.ts +++ b/packages/api/test/image-detection.test.ts @@ -11,21 +11,21 @@ import { describe('Image Detection Routes', () => { describe('Authentication', () => { it('POST /packs/analyze-image requires auth', async () => { - const res = await api('/packs/analyze-image', httpMethods.post('', {})); + const res = await api('/packs/analyze-image', httpMethods.post({})); expectUnauthorized(res); }); }); describe('POST /packs/analyze-image', () => { it('requires image parameter', async () => { - const res = await apiWithAuth('/packs/analyze-image', httpMethods.post('', {})); + const res = await apiWithAuth('/packs/analyze-image', httpMethods.post({})); expect(res.status).toBe(400); }); it('requires valid image key format', async () => { const res = await apiWithAuth( '/packs/analyze-image', - httpMethods.post('', { + httpMethods.post({ image: 'not-a-valid-url', }), ); @@ -35,7 +35,7 @@ describe('Image Detection Routes', () => { it('accepts valid request with minimal parameters', async () => { const res = await apiWithAuth( '/packs/analyze-image', - httpMethods.post('', { + httpMethods.post({ image: `${TEST_USER.id}-Ly81kadKndZ1pH2miQu8A.jpg`, }), ); @@ -49,7 +49,7 @@ describe('Image Detection Routes', () => { it('accepts matchLimit parameter', async () => { const res = await apiWithAuth( '/packs/analyze-image', - httpMethods.post('', { + httpMethods.post({ image: `${TEST_USER.id}-Ly81kadKndZ1pH2miQu8A.jpg`, matchLimit: 5, }), diff --git a/packages/api/test/pack-templates.test.ts b/packages/api/test/pack-templates.test.ts index 752cf3858a..2b46e7dd2b 100644 --- a/packages/api/test/pack-templates.test.ts +++ b/packages/api/test/pack-templates.test.ts @@ -21,17 +21,17 @@ describe('Pack Templates Routes', () => { }); describe('Authentication', () => { it('GET /pack-templates requires auth', async () => { - const res = await api('/pack-templates', httpMethods.get('')); + const res = await api('/pack-templates', httpMethods.get()); expectUnauthorized(res); }); it('GET /pack-templates/:id requires auth', async () => { - const res = await api('/pack-templates/1', httpMethods.get('')); + const res = await api('/pack-templates/1', httpMethods.get()); expectUnauthorized(res); }); it('GET /pack-templates/:id/items requires auth', async () => { - const res = await api('/pack-templates/1/items', httpMethods.get('')); + const res = await api('/pack-templates/1/items', httpMethods.get()); expectUnauthorized(res); }); }); @@ -84,7 +84,7 @@ describe('Pack Templates Routes', () => { it('returns template items list', async () => { // Seed a template with items const seededTemplate = await seedPackTemplate(); - await seedPackTemplateItems(seededTemplate.id, 3); + await seedPackTemplateItems(seededTemplate.id, { count: 3 }); const res = await apiWithAuth(`/pack-templates/${seededTemplate.id}/items`); diff --git a/packages/api/test/packs.test.ts b/packages/api/test/packs.test.ts index b49ee4f409..7bb0aad6c9 100644 --- a/packages/api/test/packs.test.ts +++ b/packages/api/test/packs.test.ts @@ -90,27 +90,27 @@ describe('Packs Routes', () => { describe('Authentication', () => { it('GET /packs requires auth', async () => { - const res = await api('/packs', httpMethods.get('')); + const res = await api('/packs', httpMethods.get()); expectUnauthorized(res); }); it('GET /packs/:id requires auth', async () => { - const res = await api('/packs/1', httpMethods.get('')); + const res = await api('/packs/1', httpMethods.get()); expectUnauthorized(res); }); it('POST /packs requires auth', async () => { - const res = await api('/packs', httpMethods.post('', {})); + const res = await api('/packs', httpMethods.post({})); expectUnauthorized(res); }); it('PUT /packs/:id requires auth', async () => { - const res = await api('/packs/1', httpMethods.put('', {})); + const res = await api('/packs/1', httpMethods.put({})); expectUnauthorized(res); }); it('DELETE /packs/:id requires auth', async () => { - const res = await api('/packs/1', httpMethods.delete('')); + const res = await api('/packs/1', httpMethods.delete()); expectUnauthorized(res); }); }); @@ -160,7 +160,7 @@ describe('Packs Routes', () => { localUpdatedAt: new Date().toISOString(), }; - const res = await apiWithAuth('/packs', httpMethods.post('', newPack)); + const res = await apiWithAuth('/packs', httpMethods.post(newPack)); expect([200, 201]).toContain(res.status); const data = await expectJsonResponse(res, ['id']); @@ -168,14 +168,14 @@ describe('Packs Routes', () => { }); it('validates required fields', async () => { - const res = await apiWithAuth('/packs', httpMethods.post('', {})); + const res = await apiWithAuth('/packs', httpMethods.post({})); expectBadRequest(res); }); it('validates name field', async () => { const res = await apiWithAuth( '/packs', - httpMethods.post('', { + httpMethods.post({ id: `pack_test_${Date.now()}`, description: 'Pack without name', category: 'hiking', @@ -195,7 +195,7 @@ describe('Packs Routes', () => { activity: 'backpacking', }; - const res = await apiWithAuth(`/packs/${testPackId}`, httpMethods.put('', updateData)); + const res = await apiWithAuth(`/packs/${testPackId}`, httpMethods.put(updateData)); expect(res.status).toBe(200); const data = await expectJsonResponse(res); @@ -205,7 +205,7 @@ describe('Packs Routes', () => { it('returns 404 for non-existent pack', async () => { const res = await apiWithAuth( '/packs/non_existent_pack_id_999', - httpMethods.put('', { + httpMethods.put({ name: 'Updated Pack', }), ); @@ -228,7 +228,7 @@ describe('Packs Routes', () => { const res = await apiWithAuth( `/packs/${otherUserPack.id}`, - httpMethods.put('', { + httpMethods.put({ name: 'Attempting to update', }), ); @@ -247,13 +247,13 @@ describe('Packs Routes', () => { category: 'hiking', }); - const res = await apiWithAuth(`/packs/${packToDelete.id}`, httpMethods.delete('')); + const res = await apiWithAuth(`/packs/${packToDelete.id}`, httpMethods.delete()); expect([200, 204]).toContain(res.status); }); it('returns 404 for non-existent pack', async () => { - const res = await apiWithAuth('/packs/non_existent_pack_id_999', httpMethods.delete('')); + const res = await apiWithAuth('/packs/non_existent_pack_id_999', httpMethods.delete()); // Soft delete might return 200 even for non-existent packs expect([200, 404]).toContain(res.status); }); @@ -272,7 +272,7 @@ describe('Packs Routes', () => { category: 'hiking', }); - const res = await apiWithAuth(`/packs/${otherUserPack.id}`, httpMethods.delete('')); + const res = await apiWithAuth(`/packs/${otherUserPack.id}`, httpMethods.delete()); // Should return 404 (not found for this user) or 403 (forbidden) expect([403, 404]).toContain(res.status); @@ -303,7 +303,7 @@ describe('Packs Routes', () => { notes: 'Extra item for safety', }; - const res = await apiWithAuth(`/packs/${testPackId}/items`, httpMethods.post('', newItem)); + const res = await apiWithAuth(`/packs/${testPackId}/items`, httpMethods.post(newItem)); expect([200, 201]).toContain(res.status); const data = await expectJsonResponse(res, ['id']); @@ -311,7 +311,7 @@ describe('Packs Routes', () => { }); it('validates required fields', async () => { - const res = await apiWithAuth(`/packs/${testPackId}/items`, httpMethods.post('', {})); + const res = await apiWithAuth(`/packs/${testPackId}/items`, httpMethods.post({})); expectBadRequest(res); }); }); @@ -325,7 +325,7 @@ describe('Packs Routes', () => { const res = await apiWithAuth( `/packs/items/${testPackItemId}`, - httpMethods.patch('', updateData), + httpMethods.patch(updateData), ); expect(res.status).toBe(200); @@ -341,7 +341,7 @@ describe('Packs Routes', () => { category: 'gear', }); - const res = await apiWithAuth(`/packs/items/${itemToDelete.id}`, httpMethods.delete('')); + const res = await apiWithAuth(`/packs/items/${itemToDelete.id}`, httpMethods.delete()); expect([200, 204]).toContain(res.status); }); @@ -354,16 +354,13 @@ describe('Packs Routes', () => { count: 2, }; - const res = await apiWithAdmin( - '/packs/generate-packs', - httpMethods.post('', generateRequest), - ); + const res = await apiWithAdmin('/packs/generate-packs', httpMethods.post(generateRequest)); expect(res.status).toBe(200); }); it('uses default params', async () => { - const res = await apiWithAdmin('/packs/generate-packs', httpMethods.post('', {})); + const res = await apiWithAdmin('/packs/generate-packs', httpMethods.post({})); expect(res.status).toBe(200); }); @@ -371,7 +368,7 @@ describe('Packs Routes', () => { it('requires admin privileges', async () => { const res = await apiWithAuth( '/packs/generate-packs', - httpMethods.post('', { + httpMethods.post({ count: 1, }), ); diff --git a/packages/api/test/upload.test.ts b/packages/api/test/upload.test.ts index be99234c0d..0f0e1ecf9c 100644 --- a/packages/api/test/upload.test.ts +++ b/packages/api/test/upload.test.ts @@ -15,12 +15,12 @@ describe('Upload Routes', () => { describe('Authentication', () => { it('requires auth for presigned URL generation', async () => { - const res = await api('/upload/presigned', httpMethods.get('')); + const res = await api('/upload/presigned', httpMethods.get()); expectUnauthorized(res); }); it('requires auth for direct upload', async () => { - const res = await api('/upload', httpMethods.post('', {})); + const res = await api('/upload', httpMethods.post({})); expectUnauthorized(res); }); }); diff --git a/packages/api/test/utils/db-helpers.ts b/packages/api/test/utils/db-helpers.ts index a9b43cf79d..c0f32aeb8a 100644 --- a/packages/api/test/utils/db-helpers.ts +++ b/packages/api/test/utils/db-helpers.ts @@ -186,9 +186,9 @@ export async function seedPackTemplateItem( export async function seedPackTemplateItems( packTemplateId: string, - count: number, - overrides?: Partial>, + opts: { count: number; overrides?: Partial> }, ) { + const { count, overrides } = opts; const db = createDb({} as unknown as Context); const items = Array.from({ length: count }, (_, i) => { @@ -269,9 +269,9 @@ export async function seedPackItem( export async function seedPackItems( packId: string, - count: number, - overrides?: Partial>, + opts: { count: number; overrides?: Partial> }, ) { + const { count, overrides } = opts; const db = createDb({} as unknown as Context); const items = Array.from({ length: count }, (_, i) => { diff --git a/packages/api/test/utils/test-helpers.ts b/packages/api/test/utils/test-helpers.ts index de8057d882..a843787a0b 100644 --- a/packages/api/test/utils/test-helpers.ts +++ b/packages/api/test/utils/test-helpers.ts @@ -34,12 +34,12 @@ export const TEST_ADMIN = { export const api = (path: string, init?: RequestInit) => app.fetch(new Request(`http://localhost/api${path}`, init)); -// Helper to create requests with authentication token -export const apiWithAuth = async ( +// Internal: shared fetch with auth token for a specific user +const fetchWithUser = async ( path: string, - init?: RequestInit, - user: typeof TEST_USER | typeof TEST_ADMIN = TEST_USER, + opts: { user: typeof TEST_USER | typeof TEST_ADMIN; init?: RequestInit }, ) => { + const { user, init } = opts; const token = await sign({ userId: user.id, role: user.role }, 'secret'); return app.fetch( new Request(`http://localhost/api${path}`, { @@ -53,10 +53,19 @@ export const apiWithAuth = async ( ); }; +// Helper to create requests with authentication token (as TEST_USER by default) +export const apiWithAuth = async (path: string, init?: RequestInit) => + fetchWithUser(path, { user: TEST_USER, init }); + +// Helper to create requests authenticated as a specific user +export const apiWithAuthAs = async ( + path: string, + opts: { user: typeof TEST_USER | typeof TEST_ADMIN; init?: RequestInit }, +) => fetchWithUser(path, opts); + // Helper to create admin authenticated requests -export const apiWithAdmin = async (path: string, init?: RequestInit) => { - return apiWithAuth(path, init, TEST_ADMIN); -}; +export const apiWithAdmin = async (path: string, init?: RequestInit) => + fetchWithUser(path, { user: TEST_ADMIN, init }); // Helper for basic auth (admin routes) export const apiWithBasicAuth = (path: string, init?: RequestInit) => { @@ -81,23 +90,23 @@ export const createTestRequestBody = (data: unknown) => ({ // Helper to test common HTTP methods export const httpMethods = { - get: (_url: string, options?: RequestInit) => ({ method: 'GET', ...options }), - post: (_url: string, body?: unknown, options?: RequestInit) => ({ + get: (options?: RequestInit) => ({ method: 'GET', ...options }), + post: (body?: unknown, options?: RequestInit) => ({ method: 'POST', ...createTestRequestBody(body), ...options, }), - put: (_url: string, body?: unknown, options?: RequestInit) => ({ + put: (body?: unknown, options?: RequestInit) => ({ method: 'PUT', ...createTestRequestBody(body), ...options, }), - patch: (_url: string, body?: unknown, options?: RequestInit) => ({ + patch: (body?: unknown, options?: RequestInit) => ({ method: 'PATCH', ...createTestRequestBody(body), ...options, }), - delete: (_url: string, options?: RequestInit) => ({ method: 'DELETE', ...options }), + delete: (options?: RequestInit) => ({ method: 'DELETE', ...options }), }; // Common test scenarios diff --git a/packages/guards/src/enum.ts b/packages/guards/src/enum.ts index f430852a85..1d832df912 100644 --- a/packages/guards/src/enum.ts +++ b/packages/guards/src/enum.ts @@ -28,9 +28,9 @@ export const makeEnumGuard = */ export function assertEnum( value: unknown, - members: readonly T[], - name = 'value', + opts: { members: readonly T[]; name?: string }, ): asserts value is T { + const { members, name = 'value' } = opts; if (typeof value !== 'string' || !(members as readonly string[]).includes(value)) { throw new Error(`Invalid ${name}: expected one of ${members.join(', ')}, got ${String(value)}`); }