diff --git a/packages/database/src/helpers/assets.ts b/packages/database/src/helpers/assets.ts index 12295c430..a00be7793 100644 --- a/packages/database/src/helpers/assets.ts +++ b/packages/database/src/helpers/assets.ts @@ -25,7 +25,8 @@ export type GenerateAssetMessagesInput = z.infer { - try { - // First verify the message exists and is not deleted - const existingMessage = await db - .select({ id: messages.id }) - .from(messages) - .where(and(eq(messages.id, messageId), isNull(messages.deletedAt))) - .limit(1); - - if (existingMessage.length === 0) { - throw new Error(`Message not found or has been deleted: ${messageId}`); - } - - await db - .update(messages) - .set({ - responseMessages, - updatedAt: new Date().toISOString(), - }) - .where(and(eq(messages.id, messageId), isNull(messages.deletedAt))); - - return { success: true }; - } catch (error) { - console.error('Failed to update message responseMessages:', error); - // Re-throw our specific validation errors - if (error instanceof Error && error.message.includes('Message not found')) { - throw error; - } - throw new Error(`Failed to update response messages for message ${messageId}`); - } -} - -/** - * Efficiently update the reasoning JSONB field for a specific message - * Optimized for frequent streaming updates - replaces entire JSONB content - * Note: reasoning field has NOT NULL constraint, so null values are not allowed - * @param messageId - The ID of the message to update - * @param reasoning - The new reasoning content (will completely replace existing) - * @returns Success status - */ -export async function updateMessageReasoning( - messageId: string, - reasoning: any -): Promise<{ success: boolean }> { - try { - // First verify the message exists and is not deleted - const existingMessage = await db - .select({ id: messages.id }) - .from(messages) - .where(and(eq(messages.id, messageId), isNull(messages.deletedAt))) - .limit(1); - - if (existingMessage.length === 0) { - throw new Error(`Message not found or has been deleted: ${messageId}`); - } - - // Validate reasoning is not null (database constraint) - if (reasoning === null || reasoning === undefined) { - throw new Error('Reasoning cannot be null - database constraint violation'); - } - - await db - .update(messages) - .set({ - reasoning, - updatedAt: new Date().toISOString(), - }) - .where(and(eq(messages.id, messageId), isNull(messages.deletedAt))); - - return { success: true }; - } catch (error) { - console.error('Failed to update message reasoning:', error); - // Re-throw our specific validation errors - if ( - error instanceof Error && - (error.message.includes('Message not found') || - error.message.includes('Reasoning cannot be null')) - ) { - throw error; - } - throw new Error(`Failed to update reasoning for message ${messageId}`); - } -} - -/** - * Efficiently update both responseMessages and reasoning JSONB fields in a single query - * Most efficient option when both fields need updating during streaming - * Note: reasoning field has NOT NULL constraint, so null values are not allowed - * @param messageId - The ID of the message to update - * @param responseMessages - The new response messages content - * @param reasoning - The new reasoning content (cannot be null) - * @returns Success status - */ -export async function updateMessageStreamingFields( - messageId: string, - responseMessages: any, - reasoning: any -): Promise<{ success: boolean }> { - try { - // First verify the message exists and is not deleted - const existingMessage = await db - .select({ id: messages.id }) - .from(messages) - .where(and(eq(messages.id, messageId), isNull(messages.deletedAt))) - .limit(1); - - if (existingMessage.length === 0) { - throw new Error(`Message not found or has been deleted: ${messageId}`); - } - - // Validate reasoning is not null (database constraint) - if (reasoning === null || reasoning === undefined) { - throw new Error('Reasoning cannot be null - database constraint violation'); - } - - await db - .update(messages) - .set({ - responseMessages, - reasoning, - updatedAt: new Date().toISOString(), - }) - .where(and(eq(messages.id, messageId), isNull(messages.deletedAt))); - - return { success: true }; - } catch (error) { - console.error('Failed to update message streaming fields:', error); - // Re-throw our specific validation errors - if ( - error instanceof Error && - (error.message.includes('Message not found') || - error.message.includes('Reasoning cannot be null')) - ) { - throw error; - } - throw new Error(`Failed to update streaming fields for message ${messageId}`); - } -} - /** * Flexibly update message fields - only updates fields that are provided * Allows updating responseMessages, reasoning, and/or rawLlmMessages in a single query @@ -254,9 +105,10 @@ export async function updateMessageStreamingFields( export async function updateMessageFields( messageId: string, fields: { - responseMessages?: any; - reasoning?: any; - rawLlmMessages?: any; + //TODO: Dallin let's make a type for this. It should not just be a jsonb object. + responseMessages?: unknown; + reasoning?: unknown; + rawLlmMessages?: unknown; finalReasoningMessage?: string; } ): Promise<{ success: boolean }> { @@ -267,7 +119,13 @@ export async function updateMessageFields( } // Build update object with only provided fields - const updateData: any = { + const updateData: { + updatedAt: string; + responseMessages?: unknown; + reasoning?: unknown; + rawLlmMessages?: unknown; + finalReasoningMessage?: string; + } = { updatedAt: new Date().toISOString(), }; @@ -335,18 +193,16 @@ export async function updateMessage( throw new Error('Reasoning cannot be null - database constraint violation'); } - // Remove undefined fields and build update object - const updateData: any = { + const updateData = { updatedAt: new Date().toISOString(), + ...Object.fromEntries( + Object.entries(fields).filter( + ([key, value]) => + value !== undefined && key !== 'id' && key !== 'createdAt' && key !== 'deletedAt' + ) + ), }; - // Only add fields that are actually provided (not undefined) - for (const [key, value] of Object.entries(fields)) { - if (value !== undefined && key !== 'id' && key !== 'createdAt' && key !== 'deletedAt') { - updateData[key] = value; - } - } - // If updatedAt was explicitly provided, use that instead if ('updatedAt' in fields && fields.updatedAt !== undefined) { updateData.updatedAt = fields.updatedAt; diff --git a/packages/server-shared/src/metrics/charts/axisInterfaces.ts b/packages/server-shared/src/metrics/charts/axisInterfaces.ts index be60cd6cc..debc1758a 100644 --- a/packages/server-shared/src/metrics/charts/axisInterfaces.ts +++ b/packages/server-shared/src/metrics/charts/axisInterfaces.ts @@ -9,13 +9,13 @@ export const BarAndLineAxisSchema = z // the column ids to use for the category axis. If multiple column ids are provided, they will be grouped together. THE LLM SHOULD NEVER SET MULTIPLE CATEGORY COLUMNS. ONLY THE USER CAN SET THIS. category: z.array(z.string()).default([]), // if null the y axis will automatically be used, the y axis will be used for the tooltip. - tooltip: z.nullable(z.array(z.string())).default(null).optional() + tooltip: z.nullable(z.array(z.string())).default(null).optional(), }) .default({ x: [], y: [], category: [], - tooltip: null + tooltip: null, }); export const ScatterAxisSchema = z @@ -29,14 +29,14 @@ export const ScatterAxisSchema = z // the column id to use for the size range of the dots. ONLY one column id should be provided. size: z.tuple([z.string()]).or(z.array(z.string()).length(0)).default([]), // if null the y axis will automatically be used, the y axis will be used for the tooltip. - tooltip: z.nullable(z.array(z.string())).default(null) + tooltip: z.nullable(z.array(z.string())).default(null), }) .default({ x: [], y: [], size: [], category: [], - tooltip: null + tooltip: null, }); export const ComboChartAxisSchema = z @@ -50,14 +50,14 @@ export const ComboChartAxisSchema = z // the column ids to use for the category axis. If multiple column ids are provided, they will be grouped together. THE LLM SHOULD NEVER SET MULTIPLE CATEGORY COLUMNS. ONLY THE USER CAN SET THIS. category: z.array(z.string()).default([]), // if null the y axis will automatically be used, the y axis will be used for the tooltip. - tooltip: z.nullable(z.array(z.string())).default(null).optional() + tooltip: z.nullable(z.array(z.string())).default(null).optional(), }) .default({ x: [], y: [], y2: [], category: [], - tooltip: null + tooltip: null, }); export const PieChartAxisSchema = z @@ -67,19 +67,19 @@ export const PieChartAxisSchema = z // the column ids to use for the y axis. If multiple column ids are provided, they will appear as rings. The LLM should NEVER set multiple y axis columns. Only the user can set this. y: z.array(z.string()).default([]), // if null the y axis will automatically be used, the y axis will be used for the tooltip. - tooltip: z.nullable(z.array(z.string())).default(null) + tooltip: z.nullable(z.array(z.string())).default(null), }) .default({ x: [], y: [], - tooltip: null + tooltip: null, }); export const ChartEncodesSchema = z.union([ BarAndLineAxisSchema, ScatterAxisSchema, PieChartAxisSchema, - ComboChartAxisSchema + ComboChartAxisSchema, ]); // Export inferred types diff --git a/packages/server-shared/src/metrics/charts/barChartProps.ts b/packages/server-shared/src/metrics/charts/barChartProps.ts index 81d88d7c8..e184d8630 100644 --- a/packages/server-shared/src/metrics/charts/barChartProps.ts +++ b/packages/server-shared/src/metrics/charts/barChartProps.ts @@ -12,7 +12,7 @@ export const BarChartPropsSchema = z.object({ // OPTIONAL: default is group. This will only apply if the columnVisualization is set to 'bar'. barGroupType: z.nullable(z.enum(['stack', 'group', 'percentage-stack'])).default('group'), // OPTIONAL: default is false. This will only apply if is is stacked and there is either a category or multiple y axis applie to the series. - barShowTotalAtTop: z.boolean().default(false) + barShowTotalAtTop: z.boolean().default(false), }); export type BarChartProps = z.infer; diff --git a/packages/server-shared/src/metrics/charts/chatConfig.defaults.test.ts b/packages/server-shared/src/metrics/charts/chatConfig.defaults.test.ts index 1dcc70c70..7b9f560a5 100644 --- a/packages/server-shared/src/metrics/charts/chatConfig.defaults.test.ts +++ b/packages/server-shared/src/metrics/charts/chatConfig.defaults.test.ts @@ -1,9 +1,9 @@ -import { describe, expect, it } from "vitest"; -import { ChartConfigPropsSchema } from "./chartConfigProps"; -import { DEFAULT_CHART_THEME } from "./configColors"; +import { describe, expect, it } from 'vitest'; +import { ChartConfigPropsSchema } from './chartConfigProps'; +import { DEFAULT_CHART_THEME } from './configColors'; -describe("DEFAULT_CHART_CONFIG", () => { - it("should conform to BusterChartConfigPropsSchema and have expected default values", () => { +describe('DEFAULT_CHART_CONFIG', () => { + it('should conform to BusterChartConfigPropsSchema and have expected default values', () => { // Verify that DEFAULT_CHART_CONFIG is valid according to the schema const parseResult = ChartConfigPropsSchema.safeParse({}); expect(parseResult.success).toBe(true); @@ -21,9 +21,9 @@ describe("DEFAULT_CHART_CONFIG", () => { expect(config.columnSettings).toEqual({}); expect(config.columnLabelFormats).toEqual({}); expect(config.showLegend).toBeNull(); - expect(config.barLayout).toBe("vertical"); + expect(config.barLayout).toBe('vertical'); expect(config.barSortBy).toEqual([]); - expect(config.barGroupType).toBe("group"); + expect(config.barGroupType).toBe('group'); expect(config.barShowTotalAtTop).toBe(false); expect(config.lineGroupType).toBeNull(); expect(config.scatterAxis).toEqual({ @@ -40,7 +40,7 @@ describe("DEFAULT_CHART_CONFIG", () => { }); // Verify the config is a complete object with all required properties - expect(typeof config).toBe("object"); + expect(typeof config).toBe('object'); expect(config).not.toBeNull(); // Verify it has a selectedChartType (required field) diff --git a/packages/server-shared/src/metrics/charts/comboChartProps.ts b/packages/server-shared/src/metrics/charts/comboChartProps.ts index a31492696..1581406a2 100644 --- a/packages/server-shared/src/metrics/charts/comboChartProps.ts +++ b/packages/server-shared/src/metrics/charts/comboChartProps.ts @@ -3,7 +3,7 @@ import { ComboChartAxisSchema } from './axisInterfaces'; export const ComboChartPropsSchema = z.object({ // Required for Combo - comboChartAxis: ComboChartAxisSchema + comboChartAxis: ComboChartAxisSchema, }); export type ComboChartProps = z.infer; diff --git a/packages/server-shared/src/metrics/charts/configColors.ts b/packages/server-shared/src/metrics/charts/configColors.ts index d0a08000c..dca755015 100644 --- a/packages/server-shared/src/metrics/charts/configColors.ts +++ b/packages/server-shared/src/metrics/charts/configColors.ts @@ -8,5 +8,5 @@ export const DEFAULT_CHART_THEME = [ '#F3864F', '#C82184', '#31FCB4', - '#E83562' + '#E83562', ]; diff --git a/packages/server-shared/src/metrics/charts/lineChartProps.test.ts b/packages/server-shared/src/metrics/charts/lineChartProps.test.ts index 519c80822..898ca9ed6 100644 --- a/packages/server-shared/src/metrics/charts/lineChartProps.test.ts +++ b/packages/server-shared/src/metrics/charts/lineChartProps.test.ts @@ -1,5 +1,5 @@ -import { describe, it, expect } from 'vitest'; -import { LineChartPropsSchema, type LineChartProps } from './lineChartProps'; +import { describe, expect, it } from 'vitest'; +import { type LineChartProps, LineChartPropsSchema } from './lineChartProps'; describe('LineChartPropsSchema', () => { describe('valid inputs', () => { @@ -81,17 +81,17 @@ describe('LineChartPropsSchema', () => { describe('type inference', () => { it('should correctly infer LineChartProps type', () => { const validData: LineChartProps = { - lineGroupType: 'stack' + lineGroupType: 'stack', }; expect(LineChartPropsSchema.parse(validData)).toEqual(validData); const validDataWithNull: LineChartProps = { - lineGroupType: null + lineGroupType: null, }; expect(LineChartPropsSchema.parse(validDataWithNull)).toEqual(validDataWithNull); const validDataWithPercentage: LineChartProps = { - lineGroupType: 'percentage-stack' + lineGroupType: 'percentage-stack', }; expect(LineChartPropsSchema.parse(validDataWithPercentage)).toEqual(validDataWithPercentage); }); diff --git a/packages/server-shared/src/metrics/charts/lineChartProps.ts b/packages/server-shared/src/metrics/charts/lineChartProps.ts index a8dde719a..7e4d9a8e5 100644 --- a/packages/server-shared/src/metrics/charts/lineChartProps.ts +++ b/packages/server-shared/src/metrics/charts/lineChartProps.ts @@ -2,7 +2,7 @@ import { z } from 'zod/v4'; export const LineChartPropsSchema = z.object({ // OPTIONAL: default is null. This will only apply if the columnVisualization is set to 'line'. If this is set to stack it will stack the lines on top of each other. The UI has this labeled as "Show as %" - lineGroupType: z.enum(['stack', 'percentage-stack']).nullable().default(null) + lineGroupType: z.enum(['stack', 'percentage-stack']).nullable().default(null), }); export type LineChartProps = z.infer; diff --git a/packages/server-shared/src/metrics/charts/metricChartProps.ts b/packages/server-shared/src/metrics/charts/metricChartProps.ts index 6a252e2c7..4f3e58225 100644 --- a/packages/server-shared/src/metrics/charts/metricChartProps.ts +++ b/packages/server-shared/src/metrics/charts/metricChartProps.ts @@ -6,7 +6,7 @@ export const DerivedMetricTitleSchema = z.object({ // whether to display to use the key or the value in the chart useValue: z.boolean(), // OPTIONAL: default is sum - aggregate: z.enum(['sum', 'average', 'median', 'max', 'min', 'count', 'first']).default('sum') + aggregate: z.enum(['sum', 'average', 'median', 'max', 'min', 'count', 'first']).default('sum'), }); export const MetricChartPropsSchema = z.object({ @@ -21,7 +21,7 @@ export const MetricChartPropsSchema = z.object({ // OPTIONAL: default is '' metricSubHeader: z.nullable(z.union([z.string(), DerivedMetricTitleSchema])).default(null), // OPTIONAL: default is null. If null then the metricColumnId will be used in conjunction with the metricValueAggregate. If not null, then the metricValueLabel will be used. - metricValueLabel: z.nullable(z.string()).default(null) + metricValueLabel: z.nullable(z.string()).default(null), }); export type DerivedMetricTitle = z.infer; diff --git a/packages/server-shared/src/metrics/charts/pieChartProps.ts b/packages/server-shared/src/metrics/charts/pieChartProps.ts index 9116dff2c..b1f6c52e9 100644 --- a/packages/server-shared/src/metrics/charts/pieChartProps.ts +++ b/packages/server-shared/src/metrics/charts/pieChartProps.ts @@ -1,6 +1,6 @@ -import { z } from "zod/v4"; -import { PieChartAxisSchema } from "./axisInterfaces"; -import { PieSortBySchema } from "./etcInterfaces"; +import { z } from 'zod/v4'; +import { PieChartAxisSchema } from './axisInterfaces'; +import { PieSortBySchema } from './etcInterfaces'; export const PieChartPropsSchema = z.object({ // OPTIONAL: default: value @@ -8,30 +8,28 @@ export const PieChartPropsSchema = z.object({ // Required for Pie pieChartAxis: PieChartAxisSchema, // OPTIONAL: default: number - pieDisplayLabelAs: z.enum(["percent", "number"]).default("number"), + pieDisplayLabelAs: z.enum(['percent', 'number']).default('number'), // OPTIONAL: default true if donut width is set. If the data contains a percentage, set this as false. pieShowInnerLabel: z.boolean().default(true), // OPTIONAL: default: sum pieInnerLabelAggregate: z - .enum(["sum", "average", "median", "max", "min", "count"]) - .default("sum"), + .enum(['sum', 'average', 'median', 'max', 'min', 'count']) + .default('sum'), // OPTIONAL: default is null and will be the name of the pieInnerLabelAggregate pieInnerLabelTitle: z.string().nullable().default(null), // OPTIONAL: default: none - pieLabelPosition: z - .nullable(z.enum(["inside", "outside", "none"])) - .default("none"), + pieLabelPosition: z.nullable(z.enum(['inside', 'outside', 'none'])).default('none'), // OPTIONAL: default: 55 | range 0-65 | range represents percent size of the donut hole. If user asks for a pie this should be 0 pieDonutWidth: z .number() - .min(0, "Donut width must be at least 0") - .max(65, "Donut width must be at most 65") + .min(0, 'Donut width must be at least 0') + .max(65, 'Donut width must be at most 65') .default(40), // OPTIONAL: default: 2.5 | range 0-100 | If there are items that are less than this percentage of the pie, they combine to form a single slice. pieMinimumSlicePercentage: z .number() - .min(0, "Minimum slice percentage must be at least 0") - .max(100, "Minimum slice percentage must be at most 100") + .min(0, 'Minimum slice percentage must be at least 0') + .max(100, 'Minimum slice percentage must be at most 100') .default(0), }); diff --git a/packages/server-shared/src/metrics/charts/scatterChartProps.ts b/packages/server-shared/src/metrics/charts/scatterChartProps.ts index 74c74ca64..31b5acded 100644 --- a/packages/server-shared/src/metrics/charts/scatterChartProps.ts +++ b/packages/server-shared/src/metrics/charts/scatterChartProps.ts @@ -4,7 +4,7 @@ import { ScatterAxisSchema } from './axisInterfaces'; export const ScatterChartPropsSchema = z.object({ // Required for Scatter scatterAxis: ScatterAxisSchema, - scatterDotSize: z.tuple([z.number(), z.number()]).default([3, 15]) + scatterDotSize: z.tuple([z.number(), z.number()]).default([3, 15]), }); export type ScatterChartProps = z.infer; diff --git a/packages/server-shared/src/metrics/charts/tableChartProps.ts b/packages/server-shared/src/metrics/charts/tableChartProps.ts index 8f3e0961f..01ee96de8 100644 --- a/packages/server-shared/src/metrics/charts/tableChartProps.ts +++ b/packages/server-shared/src/metrics/charts/tableChartProps.ts @@ -5,7 +5,7 @@ export const TableChartPropsSchema = z.object({ tableColumnWidths: z.nullable(z.record(z.string(), z.number())).default(null), tableHeaderBackgroundColor: z.nullable(z.string()).default(null), tableHeaderFontColor: z.nullable(z.string()).default(null), - tableColumnFontColor: z.nullable(z.string()).default(null) + tableColumnFontColor: z.nullable(z.string()).default(null), }); export type TableChartProps = z.infer; diff --git a/packages/test-utils/tests/database-tests/messageUpdates.test.ts b/packages/test-utils/tests/database-tests/messageUpdates.test.ts index c203bc590..67d59ca88 100644 --- a/packages/test-utils/tests/database-tests/messageUpdates.test.ts +++ b/packages/test-utils/tests/database-tests/messageUpdates.test.ts @@ -1,12 +1,11 @@ import { getLatestMessageForChat, updateMessageReasoning, - updateMessageResponseMessages, updateMessageStreamingFields, -} from '@buster/database/src/helpers/messages'; +} from '@buster/database'; import { createTestMessageWithContext } from '@buster/test-utils'; import { afterEach, beforeEach, describe, expect, test } from 'vitest'; -import { cleanupTestEnvironment, setupTestEnvironment } from './helpers'; +import { cleanupTestEnvironment, setupTestEnvironment } from '../../src/envHelpers/env-helpers'; describe('Message Update Helpers', () => { beforeEach(async () => { @@ -17,84 +16,6 @@ describe('Message Update Helpers', () => { await cleanupTestEnvironment(); }); - describe('updateMessageResponseMessages', () => { - test('successfully updates responseMessages JSONB field', async () => { - const { messageId, chatId } = await createTestMessageWithContext(); - - const newResponseMessages = { - content: 'Updated response content', - metadata: { tokens: 150, model: 'gpt-4' }, - timestamp: new Date().toISOString(), - }; - - const result = await updateMessageResponseMessages(messageId, newResponseMessages); - - expect(result.success).toBe(true); - - // Verify the update was persisted - const updatedMessage = await getLatestMessageForChat(chatId); - expect(updatedMessage?.responseMessages).toEqual(newResponseMessages); - }); - - test('handles empty responseMessages object', async () => { - const { messageId, chatId } = await createTestMessageWithContext(); - - const emptyResponse = {}; - - const result = await updateMessageResponseMessages(messageId, emptyResponse); - - expect(result.success).toBe(true); - - const updatedMessage = await getLatestMessageForChat(chatId); - expect(updatedMessage?.responseMessages).toEqual(emptyResponse); - }); - - test('handles complex nested JSONB structure', async () => { - const { messageId, chatId } = await createTestMessageWithContext(); - - const complexResponse = { - messages: [ - { role: 'assistant', content: 'Hello' }, - { role: 'user', content: 'Hi there' }, - ], - metadata: { - tokens: 250, - reasoning: { - steps: ['analyze', 'respond'], - confidence: 0.95, - }, - }, - charts: { - type: 'bar', - data: [1, 2, 3, 4], - }, - }; - - const result = await updateMessageResponseMessages(messageId, complexResponse); - - expect(result.success).toBe(true); - - const updatedMessage = await getLatestMessageForChat(chatId); - expect(updatedMessage?.responseMessages).toEqual(complexResponse); - }); - - test('throws error for non-existent message ID', async () => { - const nonExistentId = '00000000-0000-0000-0000-000000000000'; - - await expect( - updateMessageResponseMessages(nonExistentId, { content: 'test' }) - ).rejects.toThrow(`Message not found or has been deleted: ${nonExistentId}`); - }); - - test('throws error for invalid UUID format', async () => { - const invalidId = 'invalid-uuid'; - - await expect(updateMessageResponseMessages(invalidId, { content: 'test' })).rejects.toThrow( - 'Failed to update response messages for message invalid-uuid' - ); - }); - }); - describe('updateMessageReasoning', () => { test('successfully updates reasoning JSONB field', async () => { const { messageId, chatId } = await createTestMessageWithContext(); @@ -266,43 +187,4 @@ describe('Message Update Helpers', () => { ).rejects.toThrow(`Message not found or has been deleted: ${nonExistentId}`); }); }); - - describe('Performance and Concurrency', () => { - test('handles rapid sequential updates without data corruption', async () => { - const { messageId, chatId } = await createTestMessageWithContext(); - - // Simulate streaming updates sequentially to avoid race conditions - for (let i = 0; i < 5; i++) { - const result = await updateMessageStreamingFields( - messageId, - { content: `update-${i}`, iteration: i }, - { step: i, timestamp: Date.now() } - ); - expect(result.success).toBe(true); - } - - // Final state should be consistent (last update applied) - const finalMessage = await getLatestMessageForChat(chatId); - expect((finalMessage?.responseMessages as any)?.iteration).toBe(4); - expect((finalMessage?.reasoning as any)?.step).toBe(4); - }); - - test('updates timestamp on every change', async () => { - const { messageId, chatId } = await createTestMessageWithContext(); - - const originalMessage = await getLatestMessageForChat(chatId); - const originalTimestamp = originalMessage?.updatedAt; - - // Wait a moment to ensure timestamp difference - await new Promise((resolve) => setTimeout(resolve, 10)); - - await updateMessageResponseMessages(messageId, { content: 'updated' }); - - const updatedMessage = await getLatestMessageForChat(chatId); - expect(updatedMessage?.updatedAt).not.toBe(originalTimestamp); - expect(new Date(updatedMessage?.updatedAt || '').getTime()).toBeGreaterThan( - new Date(originalTimestamp || '').getTime() - ); - }); - }); }); diff --git a/packages/test-utils/tests/database-tests/updateMessageFields.test.ts b/packages/test-utils/tests/database-tests/updateMessageFields.test.ts index 33b5a633e..0946ca60a 100644 --- a/packages/test-utils/tests/database-tests/updateMessageFields.test.ts +++ b/packages/test-utils/tests/database-tests/updateMessageFields.test.ts @@ -1,10 +1,7 @@ -import { - getLatestMessageForChat, - updateMessageFields, -} from '@buster/database/src/helpers/messages'; +import { getLatestMessageForChat, updateMessageFields } from '@buster/database'; import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { createTestMessageWithContext } from '../../src'; -import { cleanupTestEnvironment, setupTestEnvironment } from './helpers'; +import { cleanupTestEnvironment, setupTestEnvironment } from '../../src/envHelpers/env-helpers'; describe('updateMessageFields', () => { beforeEach(async () => {