Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Changed

- Switch correction endpoint to use intsch as primary with intelligentSearchApi as fallback.

## [1.81.0] - 2025-09-29

### Changed
Expand Down
150 changes: 140 additions & 10 deletions node/services/autocomplete.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {
fetchAutocompleteSuggestions,
fetchTopSearches,
fetchSearchSuggestions
import {
fetchAutocompleteSuggestions,
fetchTopSearches,
fetchSearchSuggestions,
fetchCorrection,
} from './autocomplete'
import { createContext } from '../mocks/contextFactory'

Expand Down Expand Up @@ -71,14 +72,17 @@ describe('fetchAutocompleteSuggestions', () => {
expect(ctx.vtex.logger.warn).toHaveBeenCalledWith({
message: 'Autocomplete Suggestions: Primary call failed, using fallback',
error: 'Intsch service unavailable',
args: { query: 'test' },
})
expect(response).toEqual(fallbackResult)
})

it('should throw error when both intsch and fallback fail', async () => {
const ctx = createContext({
intelligentSearchApiSettings: {
fetchAutocompleteSuggestions: new Error('Fallback service also unavailable'),
fetchAutocompleteSuggestions: new Error(
'Fallback service also unavailable'
),
},
intschSettings: {
fetchAutocompleteSuggestions: new Error('Primary service unavailable'),
Expand All @@ -103,6 +107,7 @@ describe('fetchAutocompleteSuggestions', () => {
expect(ctx.vtex.logger.warn).toHaveBeenCalledWith({
message: 'Autocomplete Suggestions: Primary call failed, using fallback',
error: 'Primary service unavailable',
args: { query: 'test' },
})
})
})
Expand Down Expand Up @@ -131,7 +136,9 @@ describe('fetchTopSearches', () => {
const result = await fetchTopSearches(ctx)

expect(ctx.clients.intsch.fetchTopSearches).toHaveBeenCalledWith()
expect(ctx.clients.intelligentSearchApi.fetchTopSearches).not.toHaveBeenCalled()
expect(
ctx.clients.intelligentSearchApi.fetchTopSearches
).not.toHaveBeenCalled()

expect(result).toEqual(intschResult)
expect(ctx.vtex.logger.warn).not.toHaveBeenCalled()
Expand All @@ -154,11 +161,14 @@ describe('fetchTopSearches', () => {
const response = await fetchTopSearches(ctx)

expect(ctx.clients.intsch.fetchTopSearches).toHaveBeenCalledWith()
expect(ctx.clients.intelligentSearchApi.fetchTopSearches).toHaveBeenCalledWith()
expect(
ctx.clients.intelligentSearchApi.fetchTopSearches
).toHaveBeenCalledWith()

expect(ctx.vtex.logger.warn).toHaveBeenCalledWith({
message: 'Top Searches: Primary call failed, using fallback',
error: 'Intsch top searches unavailable',
args: {},
})
expect(response).toEqual(fallbackResult)
})
Expand Down Expand Up @@ -190,7 +200,9 @@ describe('fetchSearchSuggestions', () => {
expect(ctx.clients.intsch.fetchSearchSuggestions).toHaveBeenCalledWith({
query: 'search',
})
expect(ctx.clients.intelligentSearchApi.fetchSearchSuggestions).not.toHaveBeenCalled()
expect(
ctx.clients.intelligentSearchApi.fetchSearchSuggestions
).not.toHaveBeenCalled()

expect(result).toEqual(intschResult)
expect(ctx.vtex.logger.warn).not.toHaveBeenCalled()
Expand All @@ -206,7 +218,9 @@ describe('fetchSearchSuggestions', () => {
fetchSearchSuggestions: fallbackResult,
},
intschSettings: {
fetchSearchSuggestions: new Error('Intsch search suggestions unavailable'),
fetchSearchSuggestions: new Error(
'Intsch search suggestions unavailable'
),
},
})

Expand All @@ -215,14 +229,130 @@ describe('fetchSearchSuggestions', () => {
expect(ctx.clients.intsch.fetchSearchSuggestions).toHaveBeenCalledWith({
query: 'search',
})
expect(ctx.clients.intelligentSearchApi.fetchSearchSuggestions).toHaveBeenCalledWith({
expect(
ctx.clients.intelligentSearchApi.fetchSearchSuggestions
).toHaveBeenCalledWith({
query: 'search',
})

expect(ctx.vtex.logger.warn).toHaveBeenCalledWith({
message: 'Search Suggestions: Primary call failed, using fallback',
error: 'Intsch search suggestions unavailable',
args: { query: 'search' },
})
expect(response).toEqual(fallbackResult)
})
})

describe('fetchCorrection', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('should call intsch as primary and return its result when successful', async () => {
const intschResult = {
correction: {
text: 'text',
highlighted: '<b>text</b>',
misspelled: true,
correction: true,
},
}

const ctx = createContext({
intelligentSearchApiSettings: {
fetchCorrection: {
correction: {
text: 'fallback',
highlighted: '<b>fallback</b>',
misspelled: true,
correction: true,
},
},
},
intschSettings: {
fetchCorrection: intschResult,
},
})

const result = await fetchCorrection(ctx, 'test')

expect(ctx.clients.intsch.fetchCorrection).toHaveBeenCalledWith({
query: 'test',
})
expect(
ctx.clients.intelligentSearchApi.fetchCorrection
).not.toHaveBeenCalled()

expect(result).toEqual(intschResult)
expect(ctx.vtex.logger.warn).not.toHaveBeenCalled()
})

it('should use fallback when intsch fails', async () => {
const fallbackResult = {
correction: {
text: 'fallback-correction',
highlighted: '<b>fallback-correction</b>',
misspelled: true,
correction: true,
},
}

const ctx = createContext({
intelligentSearchApiSettings: {
fetchCorrection: fallbackResult,
},
intschSettings: {
fetchCorrection: new Error('Intsch correction unavailable'),
},
})

const response = await fetchCorrection(ctx, 'test')

expect(ctx.clients.intsch.fetchCorrection).toHaveBeenCalledWith({
query: 'test',
})
expect(
ctx.clients.intelligentSearchApi.fetchCorrection
).toHaveBeenCalledWith({
query: 'test',
})

expect(ctx.vtex.logger.warn).toHaveBeenCalledWith({
message: 'Correction: Primary call failed, using fallback',
error: 'Intsch correction unavailable',
args: { query: 'test' },
})
expect(response).toEqual(fallbackResult)
})

it('should throw error when both intsch and fallback fail', async () => {
const ctx = createContext({
intelligentSearchApiSettings: {
fetchCorrection: new Error('Fallback correction also unavailable'),
},
intschSettings: {
fetchCorrection: new Error('Primary correction unavailable'),
},
})

await expect(fetchCorrection(ctx, 'test')).rejects.toThrow(
'Fallback correction also unavailable'
)

expect(ctx.clients.intsch.fetchCorrection).toHaveBeenCalledWith({
query: 'test',
})
expect(
ctx.clients.intelligentSearchApi.fetchCorrection
).toHaveBeenCalledWith({
query: 'test',
})

expect(ctx.vtex.logger.warn).toHaveBeenCalledWith({
message: 'Correction: Primary call failed, using fallback',
error: 'Primary correction unavailable',
args: { query: 'test' },
})
})
})
36 changes: 16 additions & 20 deletions node/services/autocomplete.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Context } from '@vtex/api'

import { compareApiResults } from '../utils/compareResults'
import type { Clients } from '../clients'

/**
Expand All @@ -10,16 +9,19 @@ async function withFallback<T>(
primaryFn: () => Promise<T>,
fallbackFn: () => Promise<T>,
logger: any,
operationName: string
operationName: string,
args?: Record<string, unknown>
): Promise<T> {
try {
return await primaryFn()
} catch (error) {
logger.warn({
message: `${operationName}: Primary call failed, using fallback`,
error: error.message,
args,
})
return await fallbackFn()

return fallbackFn()
}
}

Expand All @@ -33,7 +35,8 @@ export function fetchAutocompleteSuggestions(
() => intsch.fetchAutocompleteSuggestions({ query }),
() => intelligentSearchApi.fetchAutocompleteSuggestions({ query }),
ctx.vtex.logger,
'Autocomplete Suggestions'
'Autocomplete Suggestions',
{ query }
)
}

Expand All @@ -44,7 +47,8 @@ export function fetchTopSearches(ctx: Context<Clients>) {
() => intsch.fetchTopSearches(),
() => intelligentSearchApi.fetchTopSearches(),
ctx.vtex.logger,
'Top Searches'
'Top Searches',
{}
)
}

Expand All @@ -55,27 +59,19 @@ export function fetchSearchSuggestions(ctx: Context<Clients>, query: string) {
() => intsch.fetchSearchSuggestions({ query }),
() => intelligentSearchApi.fetchSearchSuggestions({ query }),
ctx.vtex.logger,
'Search Suggestions'
'Search Suggestions',
{ query }
)
}

export function fetchCorrection(ctx: Context<Clients>, query: string) {
const { intelligentSearchApi, intsch } = ctx.clients

return compareApiResults(
() =>
intelligentSearchApi.fetchCorrection({
query,
}),
() =>
intsch.fetchCorrection({
query,
}),
ctx.vtex.production ? 10 : 100,
return withFallback(
() => intsch.fetchCorrection({ query }),
() => intelligentSearchApi.fetchCorrection({ query }),
ctx.vtex.logger,
{
logPrefix: 'Correction',
args: { query },
}
'Correction',
{ query }
)
}
Loading