fix some broken unit tests

This commit is contained in:
Nate Kelley 2025-07-22 12:14:55 -06:00
parent cb58b5034e
commit c49bdd2426
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
11 changed files with 180 additions and 25 deletions

View File

@ -12,6 +12,7 @@
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"typecheck:watch": "tsc --noEmit --watch", "typecheck:watch": "tsc --noEmit --watch",
"test": "vitest run", "test": "vitest run",
"test:unit": "pnpm run test",
"test:watch": "vitest --watch", "test:watch": "vitest --watch",
"test:ui": "vitest --ui", "test:ui": "vitest --ui",
"test:coverage": "vitest --coverage", "test:coverage": "vitest --coverage",

View File

@ -91,7 +91,7 @@ describe('Chat Query Hooks', () => {
expect(requests.getListChats).toHaveBeenCalledWith({ expect(requests.getListChats).toHaveBeenCalledWith({
admin_view: false, admin_view: false,
page_token: 0, page_token: 0,
page_size: 3500, page_size: 5000,
search: 'test' search: 'test'
}); });
}); });

View File

@ -9,10 +9,10 @@ import type { UseSupabaseUserContextType } from '@/lib/supabase';
import { timeout } from '@/lib/timeout'; import { timeout } from '@/lib/timeout';
import { useBusterNotifications } from '../BusterNotifications'; import { useBusterNotifications } from '../BusterNotifications';
import { flushSync } from 'react-dom'; import { flushSync } from 'react-dom';
import { createClient } from '@/lib/supabase/client'; import { createBrowserClient } from '@/lib/supabase/client';
const PREEMTIVE_REFRESH_MINUTES = 5; const PREEMTIVE_REFRESH_MINUTES = 5;
const supabase = createClient(); const supabase = createBrowserClient();
const useSupabaseContextInternal = ({ const useSupabaseContextInternal = ({
supabaseContext supabaseContext

View File

@ -1,14 +1,14 @@
'use client'; 'use client';
import { createBrowserClient } from '@supabase/ssr'; import { createBrowserClient as createBrowserClientSSR } from '@supabase/ssr';
export function createClient() { export function createBrowserClient() {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
if (!supabaseUrl || !supabaseAnonKey) { if (!supabaseUrl || !supabaseAnonKey) {
throw new Error('Missing Supabase environment variables'); throw new Error('Missing Supabase environment variables for browser client');
} }
return createBrowserClient(supabaseUrl, supabaseAnonKey); return createBrowserClientSSR(supabaseUrl, supabaseAnonKey);
} }

View File

@ -15,7 +15,7 @@ export async function createSupabaseServerClient() {
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
if (!supabaseUrl || !supabaseAnonKey) { if (!supabaseUrl || !supabaseAnonKey) {
throw new Error('Missing Supabase environment variables'); throw new Error('Missing Supabase environment variables for server client');
} }
const cookieStore = await cookies(); const cookieStore = await cookies();

View File

@ -1,7 +1,7 @@
import type { NextRequest } from 'next/server'; import type { NextRequest } from 'next/server';
import { BusterAuthRoutes } from './busterAuthRoutes'; import { BusterAuthRoutes } from './busterAuthRoutes';
import { BusterEmbedRoutes } from './busterEmbedRoutes'; import { BusterEmbedRoutes } from './busterEmbedRoutes';
import { BusterRoutes } from './busterRoutes'; import { BusterRoutes, type BusterRoutesWithArgsRoute } from './busterRoutes';
import { import {
createBusterRoute, createBusterRoute,
createPathnameToBusterRoute, createPathnameToBusterRoute,
@ -100,6 +100,7 @@ export const getEmbedAssetToRegularAsset = (pathnameAndQueryParams: string) => {
if (matched) { if (matched) {
const params = extractPathParamsFromRoute(pathnameAndQueryParams); const params = extractPathParamsFromRoute(pathnameAndQueryParams);
return createBusterRoute({ route: matched, ...(params as any) }); // eslint-disable-next-line @typescript-eslint/no-explicit-any -- I am just using any here because it was a pain to type this out
return createBusterRoute({ route: matched as BusterRoutes, ...(params as any) });
} }
}; };

View File

@ -59,3 +59,24 @@ vi.mock('remark-gfm', () => ({
__esModule: true, __esModule: true,
default: vi.fn() default: vi.fn()
})); }));
// Mock Supabase client to prevent environment variable errors in tests
vi.mock('@/lib/supabase/client', () => ({
createBrowserClient: vi.fn(() => ({
auth: {
refreshSession: vi.fn().mockResolvedValue({
data: {
session: {
access_token: 'mock-token',
expires_at: Date.now() / 1000 + 3600 // 1 hour from now
}
},
error: null
}),
getUser: vi.fn().mockResolvedValue({
data: { user: null },
error: null
})
}
}))
}));

View File

@ -27,8 +27,9 @@
"format": "biome format ${1:-.}", "format": "biome format ${1:-.}",
"format:fix": "biome format --write ${1:-.}", "format:fix": "biome format --write ${1:-.}",
"lint": "turbo lint", "lint": "turbo lint",
"new:package": "bun run scripts/new-package.ts", "new:package": "tsx scripts/new-package.ts",
"setup": "bash scripts/setup.sh", "setup": "bash scripts/setup.sh",
"test-before-pr": "turbo run test:unit build lint",
"test": "dotenv -e .env -- turbo test", "test": "dotenv -e .env -- turbo test",
"test:unit": "dotenv -e .env -- turbo run test:unit", "test:unit": "dotenv -e .env -- turbo run test:unit",
"test:integration": "dotenv -e .env -- turbo run test:integration", "test:integration": "dotenv -e .env -- turbo run test:integration",

View File

@ -371,8 +371,10 @@ const modifyDashboardFiles = wrapTraced(
for (const file of dashboardFilesToUpdate) { for (const file of dashboardFilesToUpdate) {
// Get current metric IDs from updated dashboard content // Get current metric IDs from updated dashboard content
const newMetricIds = (file.content as DashboardYml).rows.flatMap(row => row.items).map(item => item.id); const newMetricIds = (file.content as DashboardYml).rows
.flatMap((row) => row.items)
.map((item) => item.id);
const existingAssociations = await tx const existingAssociations = await tx
.select({ metricFileId: metricFilesToDashboardFiles.metricFileId }) .select({ metricFileId: metricFilesToDashboardFiles.metricFileId })
.from(metricFilesToDashboardFiles) .from(metricFilesToDashboardFiles)
@ -383,10 +385,12 @@ const modifyDashboardFiles = wrapTraced(
) )
) )
.execute(); .execute();
const existingMetricIds = existingAssociations.map(a => a.metricFileId); const existingMetricIds = existingAssociations.map((a) => a.metricFileId);
const addedMetricIds = newMetricIds.filter((id: string) => !existingMetricIds.includes(id)); const addedMetricIds = newMetricIds.filter(
(id: string) => !existingMetricIds.includes(id)
);
for (const metricId of addedMetricIds) { for (const metricId of addedMetricIds) {
await tx await tx
.insert(metricFilesToDashboardFiles) .insert(metricFilesToDashboardFiles)
@ -396,25 +400,30 @@ const modifyDashboardFiles = wrapTraced(
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
deletedAt: null, deletedAt: null,
createdBy: userId createdBy: userId,
}) })
.onConflictDoUpdate({ .onConflictDoUpdate({
target: [metricFilesToDashboardFiles.metricFileId, metricFilesToDashboardFiles.dashboardFileId], target: [
metricFilesToDashboardFiles.metricFileId,
metricFilesToDashboardFiles.dashboardFileId,
],
set: { set: {
deletedAt: null, deletedAt: null,
updatedAt: new Date().toISOString() updatedAt: new Date().toISOString(),
} },
}) })
.execute(); .execute();
} }
const removedMetricIds = existingMetricIds.filter((id: string) => !newMetricIds.includes(id)); const removedMetricIds = existingMetricIds.filter(
(id: string) => !newMetricIds.includes(id)
);
if (removedMetricIds.length > 0) { if (removedMetricIds.length > 0) {
await tx await tx
.update(metricFilesToDashboardFiles) .update(metricFilesToDashboardFiles)
.set({ .set({
deletedAt: new Date().toISOString(), deletedAt: new Date().toISOString(),
updatedAt: new Date().toISOString() updatedAt: new Date().toISOString(),
}) })
.where( .where(
and( and(

View File

@ -220,7 +220,9 @@ describe('Retry Mechanism Integration Tests', () => {
// The conversation should start with the original messages // The conversation should start with the original messages
const userMessages = result.conversationHistory.filter((msg) => msg.role === 'user'); const userMessages = result.conversationHistory.filter((msg) => msg.role === 'user');
const assistantMessages = result.conversationHistory.filter((msg) => msg.role === 'assistant'); const assistantMessages = result.conversationHistory.filter(
(msg) => msg.role === 'assistant'
);
expect(userMessages.length).toBeGreaterThan(0); expect(userMessages.length).toBeGreaterThan(0);
expect(assistantMessages.length).toBeGreaterThan(0); expect(assistantMessages.length).toBeGreaterThan(0);

120
scripts/test-before-pr.ts Executable file
View File

@ -0,0 +1,120 @@
#!/usr/bin/env tsx
import { execSync } from 'node:child_process';
import { performance } from 'node:perf_hooks';
interface Step {
name: string;
command: string;
emoji: string;
description: string;
}
const steps: Step[] = [
{
name: 'lint',
command: 'turbo run lint --ui=stream',
emoji: '🔍',
description: 'Running linter checks'
},
{
name: 'build',
command: 'turbo run build --ui=stream',
emoji: '🏗️',
description: 'Building all packages'
},
{
name: 'test',
command: 'turbo run test',
emoji: '🧪',
description: 'Running all tests'
}
];
function formatTime(ms: number): string {
if (ms < 1000) {
return `${Math.round(ms)}ms`;
}
const seconds = ms / 1000;
if (seconds < 60) {
return `${seconds.toFixed(1)}s`;
}
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${minutes}m ${remainingSeconds}s`;
}
function runStep(step: Step, stepNumber: number, totalSteps: number): boolean {
console.log(`\n${step.emoji} [${stepNumber}/${totalSteps}] ${step.description}...`);
console.log(`📝 Command: ${step.command}`);
const startTime = performance.now();
try {
execSync(step.command, {
stdio: 'inherit',
cwd: process.cwd()
});
const endTime = performance.now();
const duration = formatTime(endTime - startTime);
console.log(`${step.name} completed successfully in ${duration}`);
return true;
} catch (error) {
const endTime = performance.now();
const duration = formatTime(endTime - startTime);
console.log(`${step.name} failed after ${duration}`);
console.error(`💥 Error in ${step.name}:`, error);
return false;
}
}
function main() {
console.log('🚀 Starting pre-PR checks...\n');
console.log('🎯 This will run: lint → build → test');
console.log('⏱️ Grab a coffee, this might take a while!\n');
console.log('═'.repeat(50));
const overallStartTime = performance.now();
let failedSteps: string[] = [];
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
const success = runStep(step, i + 1, steps.length);
if (!success) {
failedSteps.push(step.name);
break; // Stop on first failure
}
}
const overallEndTime = performance.now();
const totalDuration = formatTime(overallEndTime - overallStartTime);
console.log('\n' + '═'.repeat(50));
if (failedSteps.length === 0) {
console.log('🎉 All pre-PR checks passed!');
console.log('✨ Your code is ready for review!');
console.log(`⏰ Total time: ${totalDuration}`);
console.log('🚢 Safe to create that PR! 🚢');
process.exit(0);
} else {
console.log('💥 Pre-PR checks failed!');
console.log(`❌ Failed steps: ${failedSteps.join(', ')}`);
console.log(`⏰ Total time: ${totalDuration}`);
console.log('🔧 Please fix the issues above before creating a PR.');
process.exit(1);
}
}
// Handle Ctrl+C gracefully
process.on('SIGINT', () => {
console.log('\n\n🛑 Pre-PR checks interrupted by user');
console.log('👋 See you next time!');
process.exit(130);
});
main();