Update integration test to include cases for bi-directional accent-insensitive search. (#14631)

## Summary

Enhanced the search resolver integration tests to comprehensively
validate bidirectional accent-insensitive search functionality. Users
can now search with or without accents and find all relevant records
regardless of how the data was originally stored.

## Changes Made

### Test Data Enhancements

- Added 2 new person IDs: `TEST_PERSON_6_ID`, `TEST_PERSON_7_ID`
- Expanded corpus from 5 to 13 records:
  - 7 persons
  - 2 companies
  - 4 pets
- Refactored test data into single arrays with content-driven variable
naming for better maintainability

### Test Coverage Improvements

- Added 6 comprehensive bidirectional tests covering French, Spanish,
and German characters
- Validated multiple fields:
- `firstName`, `lastName`, `jobTitle`, `email`, `company name`, `pet
name`
- Cross-object search testing across Person, Company, and Pet
- Updated all ranking values to account for the enlarged test corpus (5
→ 13 records)

## Test Cases Added/Updated

- [x] should find both **"José"** and **"Jose"** when searching for
`"jose"` (bidirectional accent-insensitive)
- [x] should find both **"García"** and **"Garcia"** when searching for
`"garcia"` (bidirectional accent-insensitive)
- [x] should find both accented and non-accented **"Café"** / **"Cafe"**
records when searching for `"cafe"` (bidirectional accent-insensitive)
- [x] should find both accented and non-accented **"Naïve"** /
**"Naive"** records when searching for `"naive"` (bidirectional
accent-insensitive)
- [x] should find both **"Müller"** and **"Muller"** when searching for
`"muller"` (bidirectional accent-insensitive)
- [x] should find both **"François"** and **"Francois"** when searching
for `"francois"` (bidirectional accent-insensitive)
This commit is contained in:
Abdullah.
2025-09-21 11:47:14 +05:00
committed by GitHub
parent f851879e0f
commit 646aecece1
3 changed files with 306 additions and 68 deletions
@@ -3,5 +3,7 @@ export const TEST_PERSON_2_ID = '777a8457-eb2d-40ac-a707-551b615b6981';
export const TEST_PERSON_3_ID = '777a8457-eb2d-40ac-a707-551b615b6982';
export const TEST_PERSON_4_ID = '777a8457-eb2d-40ac-a707-551b615b6983';
export const TEST_PERSON_5_ID = '777a8457-eb2d-40ac-a707-551b615b6984';
export const TEST_PERSON_6_ID = '777a8457-eb2d-40ac-a707-551b615b6985';
export const TEST_PERSON_7_ID = '777a8457-eb2d-40ac-a707-551b615b6986';
export const NOT_EXISTING_TEST_PERSON_ID =
'777a8457-eb2d-40ac-a707-551b615b6990';
@@ -1,2 +1,4 @@
export const TEST_PET_ID_1 = 'a4907cff-a582-4daf-8635-ad6c782c7c25';
export const TEST_PET_ID_2 = 'c4e97187-9b9b-4e1f-a3c5-b7883c590332';
export const TEST_PET_ID_3 = 'e4907cff-a582-4aaf-8635-ad6c782c7c26';
export const TEST_PET_ID_4 = 'f4907cff-a582-4aaf-8635-ad6c782c7c27';
@@ -1,13 +1,24 @@
import { COMPANY_GQL_FIELDS } from 'test/integration/constants/company-gql-fields.constants';
import { OBJECT_MODEL_COMMON_FIELDS } from 'test/integration/constants/object-model-common-fields';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import {
TEST_COMPANY_1_ID,
TEST_COMPANY_2_ID,
} from 'test/integration/constants/test-company-ids.constants';
import {
TEST_PERSON_1_ID,
TEST_PERSON_2_ID,
TEST_PERSON_3_ID,
TEST_PERSON_4_ID,
TEST_PERSON_5_ID,
TEST_PERSON_6_ID,
TEST_PERSON_7_ID,
} from 'test/integration/constants/test-person-ids.constants';
import {
TEST_PET_ID_1,
TEST_PET_ID_2,
TEST_PET_ID_3,
TEST_PET_ID_4,
} from 'test/integration/constants/test-pet-ids.constants';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { performCreateManyOperation } from 'test/integration/graphql/utils/perform-create-many-operation.utils';
@@ -24,17 +35,60 @@ import { type SearchResultEdgeDTO } from 'src/engine/core-modules/search/dtos/se
import { type SearchCursor } from 'src/engine/core-modules/search/services/search.service';
describe('SearchResolver', () => {
const [firstPerson, secondPerson, thirdPerson] = [
const persons = [
{ id: TEST_PERSON_1_ID, name: { firstName: 'searchInput1' } },
{ id: TEST_PERSON_2_ID, name: { firstName: 'searchInput2' } },
{ id: TEST_PERSON_3_ID, name: { firstName: 'searchInput3' } },
{
id: TEST_PERSON_4_ID,
name: { firstName: 'José', lastName: 'García' },
jobTitle: 'Café Manager',
emails: { primaryEmail: 'josé@café.com' },
},
{
id: TEST_PERSON_5_ID,
name: { firstName: 'François', lastName: 'Müller' },
jobTitle: 'Manager',
emails: { primaryEmail: 'françois@naïve.com' },
},
{
id: TEST_PERSON_6_ID,
name: { firstName: 'Jose', lastName: 'Garcia' },
jobTitle: 'Cafe Manager',
emails: { primaryEmail: 'jose@cafe.com' },
},
{
id: TEST_PERSON_7_ID,
name: { firstName: 'Francois', lastName: 'Muller' },
jobTitle: 'Manager',
emails: { primaryEmail: 'francois@naive.com' },
},
];
const [firstPet, secondPet] = [
const companies = [
{ id: TEST_COMPANY_1_ID, name: 'Café Corp' },
{ id: TEST_COMPANY_2_ID, name: 'Naïve Solutions' },
];
const pets = [
{ id: TEST_PET_ID_1, name: 'searchInput1' },
{ id: TEST_PET_ID_2, name: 'searchInput2' },
{ id: TEST_PET_ID_3, name: 'Café' },
{ id: TEST_PET_ID_4, name: 'Naïve' },
];
const [
searchInput1Person,
searchInput2Person,
searchInput3Person,
josePerson,
francoisPerson,
josePersonNoAccent,
francoisPersonNoAccent,
] = persons;
const [cafeCorp, naiveCorp] = companies;
const [searchInput1Pet, searchInput2Pet, cafePet, naivePet] = pets;
beforeAll(async () => {
await deleteAllRecords('person');
await deleteAllRecords('company');
@@ -52,14 +106,22 @@ describe('SearchResolver', () => {
'pet',
'pets',
OBJECT_MODEL_COMMON_FIELDS,
[firstPet, secondPet],
pets,
);
await performCreateManyOperation('person', 'people', PERSON_GQL_FIELDS, [
firstPerson,
secondPerson,
thirdPerson,
]);
await performCreateManyOperation(
'person',
'people',
PERSON_GQL_FIELDS,
persons,
);
await performCreateManyOperation(
'company',
'companies',
COMPANY_GQL_FIELDS,
companies,
);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@@ -88,19 +150,28 @@ describe('SearchResolver', () => {
},
eval: {
orderedRecordIds: [
firstPerson.id,
secondPerson.id,
thirdPerson.id,
firstPet.id,
secondPet.id,
searchInput1Person.id,
searchInput2Person.id,
searchInput3Person.id,
josePerson.id,
francoisPerson.id,
josePersonNoAccent.id,
francoisPersonNoAccent.id,
naiveCorp.id,
cafeCorp.id,
searchInput1Pet.id,
searchInput2Pet.id,
cafePet.id,
naivePet.id,
],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
person: thirdPerson.id,
pet: secondPet.id,
person: francoisPersonNoAccent.id,
company: cafeCorp.id,
pet: naivePet.id,
},
},
},
@@ -116,14 +187,14 @@ describe('SearchResolver', () => {
limit: 50,
},
eval: {
orderedRecordIds: [firstPerson.id, firstPet.id],
orderedRecordIds: [searchInput1Person.id, searchInput1Pet.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: firstPerson.id,
pet: firstPet.id,
person: searchInput1Person.id,
pet: searchInput1Pet.id,
},
},
},
@@ -140,13 +211,18 @@ describe('SearchResolver', () => {
limit: 50,
},
eval: {
orderedRecordIds: [firstPet.id, secondPet.id],
orderedRecordIds: [
searchInput1Pet.id,
searchInput2Pet.id,
cafePet.id,
naivePet.id,
],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
pet: secondPet.id,
pet: naivePet.id,
},
},
},
@@ -162,13 +238,21 @@ describe('SearchResolver', () => {
limit: 50,
},
eval: {
orderedRecordIds: [firstPet.id, secondPet.id],
orderedRecordIds: [
naiveCorp.id,
cafeCorp.id,
searchInput1Pet.id,
searchInput2Pet.id,
cafePet.id,
naivePet.id,
],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
pet: secondPet.id,
company: cafeCorp.id,
pet: naivePet.id,
},
},
},
@@ -181,17 +265,17 @@ describe('SearchResolver', () => {
input: {
searchInput: '',
excludedObjectNameSingulars: ['workspaceMember'],
filter: { id: { eq: firstPet.id } },
filter: { id: { eq: searchInput1Pet.id } },
limit: 50,
},
eval: {
orderedRecordIds: [firstPet.id],
orderedRecordIds: [searchInput1Pet.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
pet: firstPet.id,
pet: searchInput1Pet.id,
},
},
},
@@ -208,18 +292,17 @@ describe('SearchResolver', () => {
},
eval: {
orderedRecordIds: [
firstPerson.id,
secondPerson.id,
thirdPerson.id,
firstPet.id,
searchInput1Person.id,
searchInput2Person.id,
searchInput3Person.id,
josePerson.id,
],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
pet: firstPet.id,
person: thirdPerson.id,
person: josePerson.id,
},
},
},
@@ -235,13 +318,13 @@ describe('SearchResolver', () => {
limit: 2,
},
eval: {
orderedRecordIds: [firstPerson.id, secondPerson.id],
orderedRecordIds: [searchInput1Person.id, searchInput2Person.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
person: secondPerson.id,
person: searchInput2Person.id,
},
},
},
@@ -257,20 +340,19 @@ describe('SearchResolver', () => {
after: encodeCursorData({
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
person: secondPerson.id,
person: searchInput2Person.id,
},
}),
limit: 2,
},
eval: {
orderedRecordIds: [thirdPerson.id, firstPet.id],
orderedRecordIds: [searchInput3Person.id, josePerson.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
pet: firstPet.id,
person: thirdPerson.id,
person: josePerson.id,
},
},
},
@@ -287,18 +369,18 @@ describe('SearchResolver', () => {
},
eval: {
orderedRecordIds: [
firstPerson.id,
secondPerson.id,
thirdPerson.id,
firstPet.id,
searchInput1Person.id,
searchInput2Person.id,
searchInput3Person.id,
searchInput1Pet.id,
],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
pet: firstPet.id,
person: thirdPerson.id,
pet: searchInput1Pet.id,
person: searchInput3Person.id,
},
},
},
@@ -314,13 +396,13 @@ describe('SearchResolver', () => {
limit: 2,
},
eval: {
orderedRecordIds: [firstPerson.id, secondPerson.id],
orderedRecordIds: [searchInput1Person.id, searchInput2Person.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: secondPerson.id,
person: searchInput2Person.id,
},
},
},
@@ -337,20 +419,20 @@ describe('SearchResolver', () => {
after: encodeCursorData({
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: secondPerson.id,
person: searchInput2Person.id,
},
}),
limit: 2,
},
eval: {
orderedRecordIds: [thirdPerson.id, firstPet.id],
orderedRecordIds: [searchInput3Person.id, searchInput1Pet.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
pet: firstPet.id,
person: thirdPerson.id,
pet: searchInput1Pet.id,
person: searchInput3Person.id,
},
},
},
@@ -367,21 +449,21 @@ describe('SearchResolver', () => {
after: encodeCursorData({
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: secondPerson.id,
person: searchInput2Person.id,
},
}),
limit: 2,
filter: { id: { neq: firstPet.id } },
filter: { id: { neq: searchInput1Pet.id } },
},
eval: {
orderedRecordIds: [thirdPerson.id, secondPet.id],
orderedRecordIds: [searchInput3Person.id, searchInput2Pet.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: thirdPerson.id,
pet: secondPet.id,
person: searchInput3Person.id,
pet: searchInput2Pet.id,
},
},
},
@@ -397,13 +479,13 @@ describe('SearchResolver', () => {
limit: 1,
},
eval: {
orderedRecordIds: [firstPet.id],
orderedRecordIds: [naiveCorp.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
pet: firstPet.id,
company: naiveCorp.id,
},
},
},
@@ -420,13 +502,13 @@ describe('SearchResolver', () => {
limit: 1,
},
eval: {
orderedRecordIds: [firstPet.id],
orderedRecordIds: [searchInput1Pet.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
pet: firstPet.id,
pet: searchInput1Pet.id,
},
},
},
@@ -450,6 +532,158 @@ describe('SearchResolver', () => {
},
},
},
{
title:
'should find both "José" and "Jose" when searching for "jose" (bidirectional accent-insensitive)',
context: {
input: {
searchInput: 'jose',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [josePerson.id, josePersonNoAccent.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.12158542, tsRankCD: 0.2 },
lastRecordIdsPerObject: {
person: josePersonNoAccent.id,
},
},
},
},
},
},
{
title:
'should find both "García" and "Garcia" when searching for "garcia" (bidirectional accent-insensitive)',
context: {
input: {
searchInput: 'garcia',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [josePerson.id, josePersonNoAccent.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: josePersonNoAccent.id,
},
},
},
},
},
},
{
title:
'should find both accented and non-accented "Café"/"Cafe" records when searching for "cafe" (bidirectional accent-insensitive)',
context: {
input: {
searchInput: 'cafe',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [
josePerson.id,
josePersonNoAccent.id,
cafeCorp.id,
cafePet.id,
],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: josePersonNoAccent.id,
company: cafeCorp.id,
pet: cafePet.id,
},
},
},
},
},
},
{
title:
'should find both accented and non-accented "Naïve"/"Naive" records when searching for "naive" (bidirectional accent-insensitive)',
context: {
input: {
searchInput: 'naive',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [
francoisPerson.id,
francoisPersonNoAccent.id,
naiveCorp.id,
naivePet.id,
],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: francoisPersonNoAccent.id,
company: naiveCorp.id,
pet: naivePet.id,
},
},
},
},
},
},
{
title:
'should find both "Müller" and "Muller" when searching for "muller" (bidirectional accent-insensitive)',
context: {
input: {
searchInput: 'muller',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [francoisPerson.id, francoisPersonNoAccent.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: francoisPersonNoAccent.id,
},
},
},
},
},
},
{
title:
'should find both "François" and "Francois" when searching for "francois" (bidirectional accent-insensitive)',
context: {
input: {
searchInput: 'francois',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [francoisPerson.id, francoisPersonNoAccent.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.12158542, tsRankCD: 0.2 },
lastRecordIdsPerObject: {
person: francoisPersonNoAccent.id,
},
},
},
},
},
},
];
it.each(testsUseCases)('$title', async ({ context }) => {
@@ -497,7 +731,7 @@ describe('SearchResolver', () => {
cursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: firstPerson.id,
person: searchInput1Person.id,
},
}),
},
@@ -505,7 +739,7 @@ describe('SearchResolver', () => {
cursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: secondPerson.id,
person: searchInput2Person.id,
},
}),
},
@@ -515,7 +749,7 @@ describe('SearchResolver', () => {
endCursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: secondPerson.id,
person: searchInput2Person.id,
},
}),
},
@@ -537,9 +771,9 @@ describe('SearchResolver', () => {
excludedObjectNameSingulars: ['workspaceMember'],
limit: 2,
after: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: secondPerson.id,
person: searchInput2Person.id,
},
}),
});
@@ -552,7 +786,7 @@ describe('SearchResolver', () => {
cursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: thirdPerson.id,
person: searchInput3Person.id,
},
}),
},
@@ -560,8 +794,8 @@ describe('SearchResolver', () => {
cursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: thirdPerson.id,
pet: firstPet.id,
person: searchInput3Person.id,
pet: searchInput1Pet.id,
},
}),
},
@@ -571,8 +805,8 @@ describe('SearchResolver', () => {
endCursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: thirdPerson.id,
pet: firstPet.id,
person: searchInput3Person.id,
pet: searchInput1Pet.id,
},
}),
},