|
| 1 | +import { |
| 2 | + type BotContext, |
| 3 | + type BotRequest, |
| 4 | + type BotSecrets, |
| 5 | + type BotSettings, |
| 6 | + type ContactInfo, |
| 7 | + INPUT, |
| 8 | + type Input, |
| 9 | + PROVIDER, |
| 10 | + type ProviderType, |
| 11 | + type ResolvedPlugins, |
| 12 | + type Session, |
| 13 | + type SessionUser, |
| 14 | +} from '../models' |
| 15 | + |
| 16 | +// --------------------------------------------------------------------------- |
| 17 | +// Default values |
| 18 | +// --------------------------------------------------------------------------- |
| 19 | + |
| 20 | +export const TEST_DEFAULTS = { |
| 21 | + HUBTYPE_API_URL: 'https://api.hubtype.com', |
| 22 | + STATIC_URL: 'https://static.hubtype.com', |
| 23 | + LITELLM_API_URL: 'https://api.litellm.com', |
| 24 | + AZURE_OPENAI_API_BASE: 'https://api.openai.com', |
| 25 | + AZURE_OPENAI_API_VERSION: '2026-02-01', |
| 26 | + LANGUAGE_DETECTION_ENABLED: 'true', |
| 27 | + HUBTYPE_ACCESS_TOKEN: 'testAccessToken', |
| 28 | + LITELLM_API_KEY: 'testLiteLLMAPIKey', |
| 29 | + AZURE_OPENAI_API_KEY: 'testAzureOpenAIAPIKey', |
| 30 | + ACCESS_TOKEN: 'fake_access_token', |
| 31 | + HUBTYPE_API_HOST: 'https://api.hubtype.com', |
| 32 | + FLOW_THREAD_ID: 'testFlowThreadId', |
| 33 | + BOT_ID: 'testBotId', |
| 34 | + USER_ID: 'testUserId', |
| 35 | + ORG_ID: 'testOrgId', |
| 36 | + ORG_NAME: 'testOrg', |
| 37 | + PROVIDER: PROVIDER.WEBCHAT as ProviderType, |
| 38 | + LOCALE: 'en', |
| 39 | + COUNTRY: 'US', |
| 40 | + BOT_INTERACTION_ID: 'testInteractionId', |
| 41 | + MESSAGE_ID: 'testMessageId', |
| 42 | + LAST_ROUTE_PATH: '', |
| 43 | +} |
| 44 | + |
| 45 | +// --------------------------------------------------------------------------- |
| 46 | +// Leaf factories |
| 47 | +// --------------------------------------------------------------------------- |
| 48 | + |
| 49 | +export function createTestSettings( |
| 50 | + overrides?: Partial<BotSettings> |
| 51 | +): BotSettings { |
| 52 | + return { |
| 53 | + HUBTYPE_API_URL: TEST_DEFAULTS.HUBTYPE_API_URL, |
| 54 | + STATIC_URL: TEST_DEFAULTS.STATIC_URL, |
| 55 | + LITELLM_API_URL: TEST_DEFAULTS.LITELLM_API_URL, |
| 56 | + AZURE_OPENAI_API_BASE: TEST_DEFAULTS.AZURE_OPENAI_API_BASE, |
| 57 | + AZURE_OPENAI_API_VERSION: TEST_DEFAULTS.AZURE_OPENAI_API_VERSION, |
| 58 | + LANGUAGE_DETECTION_ENABLED: TEST_DEFAULTS.LANGUAGE_DETECTION_ENABLED, |
| 59 | + CUSTOM_SHORT_URL_HOST: null, |
| 60 | + custom: {}, |
| 61 | + ...overrides, |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +export function createTestSecrets(overrides?: Partial<BotSecrets>): BotSecrets { |
| 66 | + return { |
| 67 | + HUBTYPE_ACCESS_TOKEN: TEST_DEFAULTS.HUBTYPE_ACCESS_TOKEN, |
| 68 | + LITELLM_API_KEY: TEST_DEFAULTS.LITELLM_API_KEY, |
| 69 | + AZURE_OPENAI_API_KEY: TEST_DEFAULTS.AZURE_OPENAI_API_KEY, |
| 70 | + custom: {}, |
| 71 | + ...overrides, |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +export interface TestUserOptions { |
| 76 | + id?: string |
| 77 | + provider?: ProviderType |
| 78 | + locale?: string |
| 79 | + country?: string |
| 80 | + systemLocale?: string |
| 81 | + contactInfo?: ContactInfo[] |
| 82 | + extraData?: Record<string, any> |
| 83 | +} |
| 84 | + |
| 85 | +export function createTestSessionUser( |
| 86 | + options?: TestUserOptions |
| 87 | +): SessionUser<Record<string, any>> { |
| 88 | + const locale = options?.locale ?? TEST_DEFAULTS.LOCALE |
| 89 | + const country = options?.country ?? TEST_DEFAULTS.COUNTRY |
| 90 | + return { |
| 91 | + id: options?.id ?? TEST_DEFAULTS.USER_ID, |
| 92 | + provider: options?.provider ?? TEST_DEFAULTS.PROVIDER, |
| 93 | + locale, |
| 94 | + country, |
| 95 | + system_locale: options?.systemLocale ?? locale, |
| 96 | + contact_info: options?.contactInfo ?? [], |
| 97 | + extra_data: options?.extraData ?? {}, |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +export interface TestSessionOptions { |
| 102 | + user?: TestUserOptions |
| 103 | + botId?: string |
| 104 | + organization?: string |
| 105 | + organizationId?: string |
| 106 | + isFirstInteraction?: boolean |
| 107 | + isTestIntegration?: boolean |
| 108 | + accessToken?: string |
| 109 | + hubtypeApi?: string |
| 110 | + flowThreadId?: string |
| 111 | + retries?: number |
| 112 | + shadowing?: boolean |
| 113 | + hubtypeCaseId?: string |
| 114 | + captureUserInputNodeId?: string |
| 115 | +} |
| 116 | + |
| 117 | +export function createTestSession( |
| 118 | + options?: TestSessionOptions |
| 119 | +): Session<Record<string, any>> { |
| 120 | + return { |
| 121 | + bot: { id: options?.botId ?? TEST_DEFAULTS.BOT_ID }, |
| 122 | + user: createTestSessionUser(options?.user), |
| 123 | + organization: options?.organization ?? TEST_DEFAULTS.ORG_NAME, |
| 124 | + organization_id: options?.organizationId ?? TEST_DEFAULTS.ORG_ID, |
| 125 | + is_first_interaction: options?.isFirstInteraction ?? false, |
| 126 | + is_test_integration: options?.isTestIntegration ?? false, |
| 127 | + _access_token: options?.accessToken ?? TEST_DEFAULTS.ACCESS_TOKEN, |
| 128 | + _hubtype_api: options?.hubtypeApi ?? TEST_DEFAULTS.HUBTYPE_API_HOST, |
| 129 | + flow_thread_id: options?.flowThreadId ?? TEST_DEFAULTS.FLOW_THREAD_ID, |
| 130 | + __retries: options?.retries ?? 0, |
| 131 | + _shadowing: options?.shadowing, |
| 132 | + _hubtype_case_id: options?.hubtypeCaseId, |
| 133 | + capture_user_input: options?.captureUserInputNodeId |
| 134 | + ? { node_id: options.captureUserInputNodeId } |
| 135 | + : undefined, |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +export interface TestInputOptions { |
| 140 | + type?: Input['type'] |
| 141 | + data?: string |
| 142 | + text?: string |
| 143 | + payload?: string |
| 144 | + src?: string |
| 145 | + botInteractionId?: string |
| 146 | + messageId?: string |
| 147 | + context?: Input['context'] |
| 148 | + transcript?: string |
| 149 | +} |
| 150 | + |
| 151 | +export function createTestInput(options?: TestInputOptions): Input { |
| 152 | + return { |
| 153 | + type: options?.type ?? INPUT.TEXT, |
| 154 | + data: options?.data, |
| 155 | + text: options?.text, |
| 156 | + payload: options?.payload, |
| 157 | + src: options?.src, |
| 158 | + transcript: options?.transcript, |
| 159 | + context: options?.context, |
| 160 | + bot_interaction_id: |
| 161 | + options?.botInteractionId ?? TEST_DEFAULTS.BOT_INTERACTION_ID, |
| 162 | + message_id: options?.messageId ?? TEST_DEFAULTS.MESSAGE_ID, |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +// --------------------------------------------------------------------------- |
| 167 | +// Composite factories |
| 168 | +// --------------------------------------------------------------------------- |
| 169 | + |
| 170 | +export interface TestBotRequestOptions { |
| 171 | + input?: TestInputOptions | Input |
| 172 | + session?: TestSessionOptions | Session |
| 173 | + lastRoutePath?: string |
| 174 | + settings?: Partial<BotSettings> | BotSettings |
| 175 | + secrets?: Partial<BotSecrets> | BotSecrets |
| 176 | +} |
| 177 | + |
| 178 | +function isFullInput(v: TestInputOptions | Input): v is Input { |
| 179 | + return 'bot_interaction_id' in v && 'message_id' in v |
| 180 | +} |
| 181 | + |
| 182 | +function isFullSession(v: TestSessionOptions | Session): v is Session { |
| 183 | + return 'bot' in v && '__retries' in v |
| 184 | +} |
| 185 | + |
| 186 | +function isFullSettings( |
| 187 | + v: Partial<BotSettings> | BotSettings |
| 188 | +): v is BotSettings { |
| 189 | + return ( |
| 190 | + 'HUBTYPE_API_URL' in v && |
| 191 | + 'STATIC_URL' in v && |
| 192 | + 'LITELLM_API_URL' in v && |
| 193 | + 'AZURE_OPENAI_API_BASE' in v && |
| 194 | + 'AZURE_OPENAI_API_VERSION' in v && |
| 195 | + 'LANGUAGE_DETECTION_ENABLED' in v && |
| 196 | + 'CUSTOM_SHORT_URL_HOST' in v && |
| 197 | + 'custom' in v |
| 198 | + ) |
| 199 | +} |
| 200 | + |
| 201 | +function isFullSecrets( |
| 202 | + v: Partial<BotSecrets> | BotSecrets |
| 203 | +): v is BotSecrets { |
| 204 | + return ( |
| 205 | + 'HUBTYPE_ACCESS_TOKEN' in v && |
| 206 | + 'LITELLM_API_KEY' in v && |
| 207 | + 'AZURE_OPENAI_API_KEY' in v && |
| 208 | + 'custom' in v |
| 209 | + ) |
| 210 | +} |
| 211 | + |
| 212 | +export function createTestBotRequest( |
| 213 | + options?: TestBotRequestOptions |
| 214 | +): BotRequest { |
| 215 | + const input = |
| 216 | + options?.input == null |
| 217 | + ? createTestInput() |
| 218 | + : isFullInput(options.input) |
| 219 | + ? options.input |
| 220 | + : createTestInput(options.input) |
| 221 | + |
| 222 | + const session = |
| 223 | + options?.session == null |
| 224 | + ? createTestSession() |
| 225 | + : isFullSession(options.session) |
| 226 | + ? options.session |
| 227 | + : createTestSession(options.session) |
| 228 | + |
| 229 | + const settings = |
| 230 | + options?.settings == null |
| 231 | + ? createTestSettings() |
| 232 | + : isFullSettings(options.settings) |
| 233 | + ? options.settings |
| 234 | + : createTestSettings(options.settings) |
| 235 | + |
| 236 | + const secrets = |
| 237 | + options?.secrets == null |
| 238 | + ? createTestSecrets() |
| 239 | + : isFullSecrets(options.secrets) |
| 240 | + ? options.secrets |
| 241 | + : createTestSecrets(options.secrets) |
| 242 | + |
| 243 | + return { |
| 244 | + input, |
| 245 | + session, |
| 246 | + lastRoutePath: options?.lastRoutePath ?? TEST_DEFAULTS.LAST_ROUTE_PATH, |
| 247 | + settings, |
| 248 | + secrets, |
| 249 | + } |
| 250 | +} |
| 251 | + |
| 252 | +export interface TestBotContextOptions< |
| 253 | + TPlugins extends ResolvedPlugins = ResolvedPlugins, |
| 254 | +> extends TestBotRequestOptions { |
| 255 | + plugins?: TPlugins |
| 256 | + params?: Record<string, string> |
| 257 | + defaultDelay?: number |
| 258 | + defaultTyping?: number |
| 259 | +} |
| 260 | + |
| 261 | +export function createTestBotContext< |
| 262 | + TPlugins extends ResolvedPlugins = ResolvedPlugins, |
| 263 | +>( |
| 264 | + options?: TestBotContextOptions<TPlugins> |
| 265 | +): BotContext<TPlugins> { |
| 266 | + const request = createTestBotRequest(options) |
| 267 | + |
| 268 | + const sessionForLocale = |
| 269 | + options?.session == null |
| 270 | + ? createTestSession() |
| 271 | + : isFullSession(options.session) |
| 272 | + ? options.session |
| 273 | + : createTestSession(options.session) |
| 274 | + |
| 275 | + const locale = sessionForLocale.user.locale |
| 276 | + const country = sessionForLocale.user.country |
| 277 | + const systemLocale = sessionForLocale.user.system_locale |
| 278 | + |
| 279 | + return { |
| 280 | + ...request, |
| 281 | + plugins: options?.plugins ?? ({} as TPlugins), |
| 282 | + params: options?.params ?? {}, |
| 283 | + defaultDelay: options?.defaultDelay ?? 0, |
| 284 | + defaultTyping: options?.defaultTyping ?? 0, |
| 285 | + getUserLocale: () => locale, |
| 286 | + getUserCountry: () => country, |
| 287 | + getSystemLocale: () => systemLocale, |
| 288 | + setUserLocale: () => undefined, |
| 289 | + setUserCountry: () => undefined, |
| 290 | + setSystemLocale: () => undefined, |
| 291 | + } |
| 292 | +} |
| 293 | + |
| 294 | +/** |
| 295 | + * Alias for `createTestBotContext` typed as `PluginPreRequest`. |
| 296 | + * PluginPreRequest === BotContext in @botonic/core. |
| 297 | + */ |
| 298 | +export const createTestPluginPreRequest = createTestBotContext |
| 299 | + |
| 300 | +export interface TestPluginPostRequestOptions< |
| 301 | + TPlugins extends ResolvedPlugins = ResolvedPlugins, |
| 302 | +> extends TestBotContextOptions<TPlugins> { |
| 303 | + response?: string | null |
| 304 | +} |
| 305 | + |
| 306 | +export function createTestPluginPostRequest< |
| 307 | + TPlugins extends ResolvedPlugins = ResolvedPlugins, |
| 308 | +>( |
| 309 | + options?: TestPluginPostRequestOptions<TPlugins> |
| 310 | +): BotContext<TPlugins> & { response: string | null } { |
| 311 | + return { |
| 312 | + ...createTestBotContext(options), |
| 313 | + response: options?.response ?? null, |
| 314 | + } |
| 315 | +} |
0 commit comments