Files
upstream-twenty/packages/twenty-server/test/integration/graphql/suites/search/search-resolver.integration-spec.ts
upstream-mirror e7da97194a
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s
snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
2026-09-17 23:28:13 +05:00

1488 lines
40 KiB
TypeScript

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,
TEST_PERSON_8_ID,
TEST_PERSON_9_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,
TEST_PET_ID_5,
} from 'test/integration/constants/test-pet-ids.constants';
import { createManyOperation } from 'test/integration/graphql/utils/create-many-operation.util';
import { search } from 'test/integration/graphql/utils/search.util';
import { deleteAllRecords } from 'test/integration/utils/delete-all-records';
import { waitForAllJobsToFinish } from 'test/integration/utils/wait-for-all-jobs-to-finish.util';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import {
decodeCursor,
encodeCursorData,
} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
import { type SearchArgs } from 'src/engine/core-modules/search/dtos/search-args';
import { type SearchResultEdgeDTO } from 'src/engine/core-modules/search/dtos/search-result-edge.dto';
import { type SearchCursor } from 'src/engine/core-modules/search/services/search.service';
describe('SearchResolver', () => {
const persons = [
{
id: TEST_PERSON_1_ID,
name: { firstName: 'searchInput1' },
phones: {
primaryPhoneNumber: '2071234567',
primaryPhoneCallingCode: '+44',
primaryPhoneCountryCode: 'GB',
},
},
{
id: TEST_PERSON_2_ID,
name: { firstName: 'searchInput2' },
phones: {
primaryPhoneNumber: '5551234567',
primaryPhoneCallingCode: '+1',
primaryPhoneCountryCode: 'US',
additionalPhones: [],
},
},
{ 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' },
phones: {
primaryPhoneNumber: '123456789',
primaryPhoneCallingCode: '+33',
primaryPhoneCountryCode: 'FR',
},
},
{
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' },
},
{
id: TEST_PERSON_8_ID,
name: { firstName: 'MultiEmail', lastName: 'Person' },
emails: {
primaryEmail: 'primary@example.com',
additionalEmails: ['secondary@example.com', 'work@company.org'],
},
},
{
id: TEST_PERSON_9_ID,
name: { firstName: 'MultiPhone', lastName: 'Person' },
emails: {
primaryEmail: 'empty@arrays.com',
additionalEmails: [],
},
phones: {
primaryPhoneNumber: '9998887777',
primaryPhoneCallingCode: '+1',
primaryPhoneCountryCode: 'US',
additionalPhones: [
{ number: '1112223333', countryCode: 'US', callingCode: '+1' },
{ number: '4445556666', countryCode: 'GB', callingCode: '+44' },
],
},
},
];
const companies = [
{
id: TEST_COMPANY_1_ID,
name: 'Café Corp',
domainName: {
primaryLinkLabel: 'Main Site',
primaryLinkUrl: 'https://links.example.com',
secondaryLinks: [
{ label: 'DocsPortal', url: 'docs.links.example.com' },
{ label: 'SupportHub', url: 'support.links.example.com' },
],
},
},
{
id: TEST_COMPANY_2_ID,
name: 'Naïve Solutions',
domainName: {
primaryLinkLabel: 'NaivePortal',
primaryLinkUrl: 'https://naive.portal.example',
secondaryLinks: [],
},
},
];
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' },
{ id: TEST_PET_ID_5, name: '示例商业线索' },
];
const [
searchInput1Person,
searchInput2Person,
searchInput3Person,
josePerson,
francoisPerson,
josePersonNoAccent,
francoisPersonNoAccent,
multiEmailPerson,
multiPhonePerson,
] = persons;
const [cafeCorp, naiveCorp] = companies;
const [searchInput1Pet, searchInput2Pet, cafePet, naivePet, cjkPet] = pets;
beforeAll(async () => {
// TODO refactor not a good practice, or should at least restore afterwards
await deleteAllRecords('person');
await deleteAllRecords('company');
await deleteAllRecords('opportunity');
await deleteAllRecords('note');
await deleteAllRecords('task');
await deleteAllRecords('noteTarget');
await deleteAllRecords('taskTarget');
await deleteAllRecords('dashboard');
await deleteAllRecords('_pet');
await deleteAllRecords('_surveyResult');
await deleteAllRecords('_rocket');
await createManyOperation({
objectMetadataSingularName: 'pet',
objectMetadataPluralName: 'pets',
gqlFields: OBJECT_MODEL_COMMON_FIELDS,
data: pets,
});
await createManyOperation({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: persons,
});
await waitForAllJobsToFinish();
await deleteAllRecords('company');
await createManyOperation({
objectMetadataSingularName: 'company',
objectMetadataPluralName: 'companies',
gqlFields: COMPANY_GQL_FIELDS,
data: companies,
});
});
const testsUseCases: EachTestingContext<{
input: SearchArgs;
eval: {
orderedRecordIds: string[];
pageInfo: {
hasNextPage: boolean;
decodedEndCursor: SearchCursor | null;
};
};
}>[] = [
{
title:
'should return all records for "isSearchable:true" objects when no search input is provided',
context: {
input: {
searchInput: '',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
'workflow',
'messageCampaign',
'messageList',
],
limit: 50,
},
eval: {
orderedRecordIds: [
searchInput1Person.id,
searchInput2Person.id,
searchInput3Person.id,
josePerson.id,
francoisPerson.id,
josePersonNoAccent.id,
francoisPersonNoAccent.id,
multiEmailPerson.id,
multiPhonePerson.id,
naiveCorp.id,
cafeCorp.id,
searchInput1Pet.id,
searchInput2Pet.id,
cjkPet.id,
cafePet.id,
naivePet.id,
],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
person: multiPhonePerson.id,
company: cafeCorp.id,
pet: naivePet.id,
},
},
},
},
},
},
{
title: 'should return filtered records when search input is provided',
context: {
input: {
searchInput: 'searchInput1',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [searchInput1Person.id, searchInput1Pet.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput1Person.id,
pet: searchInput1Pet.id,
},
},
},
},
},
},
{
title: 'should return record from included Objects only',
context: {
input: {
searchInput: '',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
includedObjectNameSingulars: ['pet'],
limit: 50,
},
eval: {
orderedRecordIds: [
searchInput1Pet.id,
searchInput2Pet.id,
cjkPet.id,
cafePet.id,
naivePet.id,
],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
pet: naivePet.id,
},
},
},
},
},
},
{
title: 'should not return record from excludedObject',
context: {
input: {
searchInput: '',
excludedObjectNameSingulars: [
'workspaceMember',
'person',
'employmentHistory',
'petCareAgreement',
'workflow',
'messageCampaign',
'messageList',
],
limit: 50,
},
eval: {
orderedRecordIds: [
naiveCorp.id,
cafeCorp.id,
searchInput1Pet.id,
searchInput2Pet.id,
cjkPet.id,
cafePet.id,
naivePet.id,
],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
company: cafeCorp.id,
pet: naivePet.id,
},
},
},
},
},
},
{
title: 'should return filtered records when filter is provided',
context: {
input: {
searchInput: '',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
filter: { id: { eq: searchInput1Pet.id } },
limit: 50,
},
eval: {
orderedRecordIds: [searchInput1Pet.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
pet: searchInput1Pet.id,
},
},
},
},
},
},
{
title: 'should limit records number with limit',
context: {
input: {
searchInput: '',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 4,
},
eval: {
orderedRecordIds: [
searchInput1Person.id,
searchInput2Person.id,
searchInput3Person.id,
josePerson.id,
],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
person: josePerson.id,
},
},
},
},
},
},
{
title: 'should return endCursor when paginating',
context: {
input: {
searchInput: '',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 2,
},
eval: {
orderedRecordIds: [searchInput1Person.id, searchInput2Person.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
person: searchInput2Person.id,
},
},
},
},
},
},
{
title: 'should return endCursor when paginating with Cursor',
context: {
input: {
searchInput: '',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
after: encodeCursorData({
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
person: searchInput2Person.id,
},
}),
limit: 2,
},
eval: {
orderedRecordIds: [searchInput3Person.id, josePerson.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
person: josePerson.id,
},
},
},
},
},
},
{
title: 'should limit records number with limit and searchInput',
context: {
input: {
searchInput: 'searchInput',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 4,
},
eval: {
orderedRecordIds: [
searchInput1Person.id,
searchInput2Person.id,
searchInput3Person.id,
searchInput1Pet.id,
],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
pet: searchInput1Pet.id,
person: searchInput3Person.id,
},
},
},
},
},
},
{
title: 'should return endCursor when paginating with searchInput',
context: {
input: {
searchInput: 'searchInput',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 2,
},
eval: {
orderedRecordIds: [searchInput1Person.id, searchInput2Person.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput2Person.id,
},
},
},
},
},
},
{
title:
'should return endCursor when paginating with searchInput with Cursor',
context: {
input: {
searchInput: 'searchInput',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
after: encodeCursorData({
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput2Person.id,
},
}),
limit: 2,
},
eval: {
orderedRecordIds: [searchInput3Person.id, searchInput1Pet.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
pet: searchInput1Pet.id,
person: searchInput3Person.id,
},
},
},
},
},
},
{
title:
'should return endCursor when paginating with searchInput with Cursor and filter',
context: {
input: {
searchInput: 'searchInput',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
after: encodeCursorData({
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput2Person.id,
},
}),
limit: 2,
filter: { id: { neq: searchInput1Pet.id } },
},
eval: {
orderedRecordIds: [searchInput3Person.id, searchInput2Pet.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput3Person.id,
pet: searchInput2Pet.id,
},
},
},
},
},
},
{
title: 'should paginate properly with excludedObject',
context: {
input: {
searchInput: '',
excludedObjectNameSingulars: [
'workspaceMember',
'person',
'employmentHistory',
'petCareAgreement',
],
limit: 1,
},
eval: {
orderedRecordIds: [naiveCorp.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
company: naiveCorp.id,
},
},
},
},
},
},
{
title: 'should paginate properly with included Objects only',
context: {
input: {
searchInput: '',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
includedObjectNameSingulars: ['pet'],
limit: 1,
},
eval: {
orderedRecordIds: [searchInput1Pet.id],
pageInfo: {
hasNextPage: true,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
pet: searchInput1Pet.id,
},
},
},
},
},
},
{
title: 'should paginate properly when no records are returned',
context: {
input: {
searchInput: '',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 0,
},
eval: {
orderedRecordIds: [],
pageInfo: {
hasNextPage: true,
decodedEndCursor: null,
},
},
},
},
{
title:
'should find both "José" and "Jose" when searching for "jose" (bidirectional accent-insensitive)',
context: {
input: {
searchInput: 'jose',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
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',
'employmentHistory',
'petCareAgreement',
],
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',
'employmentHistory',
'petCareAgreement',
],
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',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [
naiveCorp.id,
francoisPerson.id,
francoisPersonNoAccent.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',
'employmentHistory',
'petCareAgreement',
],
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',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [francoisPerson.id, francoisPersonNoAccent.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.12158542, tsRankCD: 0.2 },
lastRecordIdsPerObject: {
person: francoisPersonNoAccent.id,
},
},
},
},
},
},
{
title: 'should find person by raw phone number',
context: {
input: {
searchInput: '2071234567',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [searchInput1Person.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput1Person.id,
},
},
},
},
},
},
{
title: 'should find person by international phone number with plus',
context: {
input: {
searchInput: '+442071234567',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [searchInput1Person.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput1Person.id,
},
},
},
},
},
},
{
title: 'should find person by international phone number without plus',
context: {
input: {
searchInput: '442071234567',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [searchInput1Person.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput1Person.id,
},
},
},
},
},
},
{
title: 'should find person by trunk prefix phone number (UK)',
context: {
input: {
searchInput: '02071234567',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [searchInput1Person.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput1Person.id,
},
},
},
},
},
},
{
title: 'should find person by trunk prefix phone number (France)',
context: {
input: {
searchInput: '0123456789',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [josePerson.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: josePerson.id,
},
},
},
},
},
},
{
title: 'should find person by US phone number (raw national)',
context: {
input: {
searchInput: '5551234567',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [searchInput2Person.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput2Person.id,
},
},
},
},
},
},
{
title: 'should find person by partial phone number',
context: {
input: {
searchInput: '555123',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [searchInput2Person.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput2Person.id,
},
},
},
},
},
},
{
title:
'should find multiple persons when phone search matches multiple records',
context: {
input: {
searchInput: '123456789',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [josePerson.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: josePerson.id,
},
},
},
},
},
},
{
title:
'should rank phone search results appropriately vs other field matches',
context: {
input: {
searchInput: 'searchInput1',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 50,
},
eval: {
orderedRecordIds: [searchInput1Person.id, searchInput1Pet.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput1Person.id,
pet: searchInput1Pet.id,
},
},
},
},
},
},
{
title: 'should find person by additional email (secondary email)',
context: {
input: {
searchInput: 'secondary@example.com',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [multiEmailPerson.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: multiEmailPerson.id,
},
},
},
},
},
},
{
title: 'should find person by additional email (work email)',
context: {
input: {
searchInput: 'work@company.org',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [multiEmailPerson.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: multiEmailPerson.id,
},
},
},
},
},
},
{
title: 'should find person by partial additional email',
context: {
input: {
searchInput: 'company.org',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [multiEmailPerson.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: multiEmailPerson.id,
},
},
},
},
},
},
{
title: 'should find person by additional phone number',
context: {
input: {
searchInput: '1112223333',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [multiPhonePerson.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: multiPhonePerson.id,
},
},
},
},
},
},
{
title: 'should find person by second additional phone number',
context: {
input: {
searchInput: '4445556666',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [multiPhonePerson.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: multiPhonePerson.id,
},
},
},
},
},
},
{
title: 'should find company by secondary link url',
context: {
input: {
searchInput: 'docs.links.example.com',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [cafeCorp.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
company: cafeCorp.id,
},
},
},
},
},
},
{
title: 'should find company by secondary link label',
context: {
input: {
searchInput: 'DocsPortal',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [cafeCorp.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
company: cafeCorp.id,
},
},
},
},
},
},
{
title: 'should handle empty additional emails array',
context: {
input: {
searchInput: 'empty@arrays.com',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [multiPhonePerson.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: multiPhonePerson.id,
},
},
},
},
},
},
{
title: 'should handle empty additional phones array',
context: {
input: {
searchInput: '5551234567',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [searchInput2Person.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput2Person.id,
},
},
},
},
},
},
{
title: 'should handle empty secondary links array',
context: {
input: {
searchInput: 'NaivePortal',
excludedObjectNameSingulars: ['workspaceMember'],
limit: 50,
},
eval: {
orderedRecordIds: [naiveCorp.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
company: naiveCorp.id,
},
},
},
},
},
},
{
title:
'should find CJK records via ILIKE fallback when tsvector tokenization fails',
context: {
input: {
searchInput: '商业',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
includedObjectNameSingulars: ['pet'],
limit: 50,
},
eval: {
orderedRecordIds: [cjkPet.id],
pageInfo: {
hasNextPage: false,
decodedEndCursor: {
lastRanks: { tsRank: 0, tsRankCD: 0 },
lastRecordIdsPerObject: {
pet: cjkPet.id,
},
},
},
},
},
},
];
it.each(eachTestingContextFilter(testsUseCases))(
'$title',
async ({ context }) => {
const response = await search({
...context.input,
expectToFail: false,
});
expect(response.data).toBeDefined();
expect(response.data.search).toBeDefined();
const searchResult = response.data.search;
const edges = searchResult.edges;
const pageInfo = searchResult.pageInfo;
if (context.eval.orderedRecordIds.length > 0) {
expect(edges).not.toHaveLength(0);
} else {
expect(edges).toHaveLength(0);
}
expect(
edges.map((edge: SearchResultEdgeDTO) => edge.node.recordId),
).toEqual(context.eval.orderedRecordIds);
expect(pageInfo).toBeDefined();
expect(context.eval.pageInfo.hasNextPage).toEqual(pageInfo.hasNextPage);
expect(context.eval.pageInfo.decodedEndCursor).toEqual(
pageInfo.endCursor
? decodeCursor(pageInfo.endCursor)
: pageInfo.endCursor,
);
},
);
it('should return cursor for each search edge', async () => {
const response = await search({
searchInput: 'searchInput',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 2,
expectToFail: false,
});
const expectedResult = {
edges: [
{
cursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: searchInput1Person.id,
},
}),
},
{
cursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: searchInput2Person.id,
},
}),
},
],
pageInfo: {
hasNextPage: true,
endCursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: searchInput2Person.id,
},
}),
},
};
expect({
...response.data.search,
edges: response.data.search.edges.map((edge: SearchResultEdgeDTO) => ({
cursor: edge.cursor,
})),
}).toEqual(expectedResult);
});
it('should return cursor for each search edge with after cursor input', async () => {
const response = await search({
searchInput: 'searchInput',
excludedObjectNameSingulars: [
'workspaceMember',
'employmentHistory',
'petCareAgreement',
],
limit: 2,
after: encodeCursorData({
lastRanks: { tsRank: 0.06079271, tsRankCD: 0.1 },
lastRecordIdsPerObject: {
person: searchInput2Person.id,
},
}),
expectToFail: false,
});
const expectedResult = {
edges: [
{
cursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: searchInput3Person.id,
},
}),
},
{
cursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: searchInput3Person.id,
pet: searchInput1Pet.id,
},
}),
},
],
pageInfo: {
hasNextPage: true,
endCursor: encodeCursorData({
lastRanks: { tsRankCD: 0.1, tsRank: 0.06079271 },
lastRecordIdsPerObject: {
person: searchInput3Person.id,
pet: searchInput1Pet.id,
},
}),
},
};
expect({
...response.data.search,
edges: response.data.search.edges.map((edge: SearchResultEdgeDTO) => ({
cursor: edge.cursor,
})),
}).toEqual(expectedResult);
});
});