fix: tests

This commit is contained in:
2023-05-31 15:05:41 +02:00
parent 0d60b8bfad
commit 68400f2480
29 changed files with 647 additions and 272 deletions

View File

@@ -56,7 +56,6 @@
"devDependencies": {
"@openstapps/es-mapping-generator": "workspace:*",
"@openstapps/eslint-config": "workspace:*",
"@openstapps/nyc-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@testdeck/mocha": "0.3.3",
@@ -91,7 +90,7 @@
"sinon-express-mock": "2.2.1",
"supertest": "6.3.3",
"tsup": "6.7.0",
"typescript": "4.6.4"
"typescript": "4.8.4"
},
"tsup": {
"entry": [

View File

@@ -20,19 +20,18 @@ import {
IndicesGetAliasResponse,
SearchHit,
SearchResponse,
} from '@elastic/elasticsearch/lib/api/types';
} from '@elastic/elasticsearch/lib/api/types.js';
import {SCConfigFile, SCSearchQuery, SCSearchResponse, SCThings, SCUuid} from '@openstapps/core';
import {Logger} from '@openstapps/logger';
import {IndicesUpdateAliasesParamsAction, SearchResponse} from 'elasticsearch';
import moment from 'moment';
import {MailQueue} from '../../notification/mail-queue';
import {Bulk} from '../bulk-storage';
import {Database} from '../database';
import {parseAggregations} from './aggregations';
import * as Monitoring from './monitoring';
import {buildQuery} from './query/query';
import {buildSort} from './query/sort';
import {aggregations, putTemplate} from './templating';
import {MailQueue} from '../../notification/mail-queue.js';
import {Bulk} from '../bulk-storage.js';
import {Database} from '../database.js';
import {parseAggregations} from './aggregations.js';
import * as Monitoring from './monitoring.js';
import {buildQuery} from './query/query.js';
import {buildSort} from './query/sort.js';
import {aggregations, putTemplate} from './templating.js';
import {
ElasticsearchConfig,
ElasticsearchQueryDisMaxConfig,
@@ -44,7 +43,7 @@ import {
INACTIVE_INDICES_ALIAS,
matchIndexByType,
VALID_INDEX_REGEX,
} from './util';
} from './util/index.js';
import {noUndefined} from './util/no-undefined.js';
import {retryCatch, RetryOptions} from './util/retry.js';
@@ -170,7 +169,7 @@ export class Elasticsearch implements Database {
return searchResponse.hits.hits[0];
}
private async prepareBulkWrite(bulk: Bulk): Promise<string> {
async prepareBulkWrite(bulk: Bulk): Promise<string> {
if (!this.ready) {
throw new Error('No connection to elasticsearch established yet.');
}
@@ -389,7 +388,7 @@ export class Elasticsearch implements Database {
index: ACTIVE_INDICES_ALIAS,
allow_no_indices: true,
size: parameters.size,
sort: typeof parameters.sort !== 'undefined' ? buildSort(parameters.sort) : undefined,
sort: typeof parameters.sort === 'undefined' ? undefined : buildSort(parameters.sort),
});
return {
@@ -401,9 +400,9 @@ export class Elasticsearch implements Database {
})
.filter(noUndefined),
facets:
typeof response.aggregations !== 'undefined'
? parseAggregations(response.aggregations as Record<AggregateName, AggregationsMultiTermsBucket>)
: [],
typeof response.aggregations === 'undefined'
? []
: parseAggregations(response.aggregations as Record<AggregateName, AggregationsMultiTermsBucket>),
pagination: {
count: response.hits.hits.length,
offset: typeof parameters.from === 'number' ? parameters.from : 0,

View File

@@ -14,7 +14,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Client} from '@elastic/elasticsearch';
import {SearchRequest} from '@elastic/elasticsearch/lib/api/types';
import {SearchRequest} from '@elastic/elasticsearch/lib/api/types.js';
import {
SCMonitoringConfiguration,
SCMonitoringLogAction,
@@ -150,6 +150,3 @@ export async function setUp(
Logger.log(`Scheduled ${monitoringConfig.watchers.length} watches`);
}
// do this for esm mocking
export default {setUp};

View File

@@ -12,7 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {QueryDslQueryContainer} from '@elastic/elasticsearch/lib/api/types';
import {QueryDslQueryContainer} from '@elastic/elasticsearch/lib/api/types.js';
import {SCConfigFile, SCSearchQuery} from '@openstapps/core';
import {ElasticsearchConfig} from '../types/elasticsearch-config.js';
import {buildFilter} from './filter.js';
@@ -31,7 +31,7 @@ export const buildQuery = function buildQuery(
defaultConfig: SCConfigFile,
elasticsearchConfig: ElasticsearchConfig,
): QueryDslQueryContainer {
// if config provides an minMatch parameter we use query_string instead of match query
// if config provides a minMatch parameter, we use query_string instead of a match query
let query;
if (typeof elasticsearchConfig.query === 'undefined') {
query = {
@@ -39,7 +39,7 @@ export const buildQuery = function buildQuery(
analyzer: 'search_german',
default_field: 'name',
minimum_should_match: '90%',
query: typeof parameters.query !== 'string' ? '*' : parameters.query,
query: typeof parameters.query === 'string' ? parameters.query : '*',
},
};
} else if (elasticsearchConfig.query.queryType === 'query_string') {
@@ -48,7 +48,7 @@ export const buildQuery = function buildQuery(
analyzer: 'search_german',
default_field: 'name',
minimum_should_match: elasticsearchConfig.query.minMatch,
query: typeof parameters.query !== 'string' ? '*' : parameters.query,
query: typeof parameters.query === 'string' ? parameters.query : '*',
},
};
} else if (elasticsearchConfig.query.queryType === 'dis_max') {

View File

@@ -12,7 +12,7 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Sort} from '@elastic/elasticsearch/lib/api/types';
import {Sort} from '@elastic/elasticsearch/lib/api/types.js';
import {SCSearchSort} from '@openstapps/core';
import {buildDistanceSort} from './sort/distance.js';
import {buildDucetSort} from './sort/ducet.js';

View File

@@ -12,14 +12,16 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {SortOptions} from '@elastic/elasticsearch/lib/api/types.js';
import {SCGenericSort} from '@openstapps/core';
/**
* Converts a generic sort to elasticsearch syntax
*
* @param sort A sorting definition
*/
export function hashStringToInt(string_: string): number {
return [...string_].reduce(
(accumulator, current) =>
(current.codePointAt(0) ?? 0) + (accumulator << 6) + (accumulator << 16) - accumulator,
0,
);
export function buildGenericSort(sort: SCGenericSort): SortOptions {
return {
[sort.arguments.field]: sort.order,
};
}

View File

@@ -24,10 +24,11 @@ import {expect} from 'chai';
import {bulk, DEFAULT_TEST_TIMEOUT} from '../common.js';
import {testApp} from '../tests-setup.js';
import {readFile} from 'fs/promises';
import {v4} from 'uuid';
const book = JSON.parse(
await readFile('node_modules/@openstapps/core/test/resources/indexable/Book.1.json', 'utf8'),
);
await readFile('node_modules/@openstapps/core/test/resources/indexable/Book.2.json', 'utf8'),
).instance;
describe('Bulk routes', async function () {
// increase timeout for the suite
@@ -60,7 +61,7 @@ describe('Bulk routes', async function () {
it('should return (throw) error if a bulk with the provided UID cannot be found when adding to a bulk', async function () {
await testApp.post(bulkRoute.urlPath).set('Content-Type', 'application/json').send(request);
const bulkAddRouteUrlPath = bulkAddRoute.urlPath.toLocaleLowerCase().replace(':uid', 'a-wrong-uid');
const bulkAddRouteUrlPath = bulkAddRoute.urlPath.toLocaleLowerCase().replace(':uid', v4());
const {status} = await testApp
.post(bulkAddRouteUrlPath)
@@ -75,10 +76,10 @@ describe('Bulk routes', async function () {
.post(bulkRoute.urlPath)
.set('Content-Type', 'application/json')
.send(request);
const bulkAddRouteurlPath = bulkAddRoute.urlPath.toLocaleLowerCase().replace(':uid', response.body.uid);
const bulkAddRouteUrlPath = bulkAddRoute.urlPath.toLocaleLowerCase().replace(':uid', response.body.uid);
const {status, body} = await testApp
.post(bulkAddRouteurlPath)
.post(bulkAddRouteUrlPath)
.set('Content-Type', 'application/json')
.send(book);
@@ -88,10 +89,10 @@ describe('Bulk routes', async function () {
it('should return (throw) error if a bulk with the provided UID cannot be found when closing a bulk (done)', async function () {
await testApp.post(bulkRoute.urlPath).set('Content-Type', 'application/json').send(request);
const bulkDoneRouteurlPath = bulkDoneRoute.urlPath.toLocaleLowerCase().replace(':uid', 'a-wrong-uid');
const bulkDoneRouteUrlPath = bulkDoneRoute.urlPath.toLocaleLowerCase().replace(':uid', 'a-wrong-uid');
const {status} = await testApp
.post(bulkDoneRouteurlPath)
.post(bulkDoneRouteUrlPath)
.set('Content-Type', 'application/json')
.send({});

View File

@@ -24,7 +24,7 @@ use(chaiAsPromised);
const book = JSON.parse(
await readFile('node_modules/@openstapps/core/test/resources/indexable/Book.1.json', 'utf8'),
);
).instance;
describe('Thing update route', async function () {
// increase timeout for the suite

View File

@@ -13,7 +13,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {AggregateName, AggregationsMultiTermsBucket} from '@elastic/elasticsearch/lib/api/types';
import {AggregateName, AggregationsMultiTermsBucket} from '@elastic/elasticsearch/lib/api/types.js';
import {SCFacet, SCThingType} from '@openstapps/core';
import {expect} from 'chai';
import {parseAggregations} from '../../../src/storage/elasticsearch/aggregations.js';

View File

@@ -15,13 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Client, Diagnostic} from '@elastic/elasticsearch';
import Indices from '@elastic/elasticsearch/lib/api/api/indices';
import {
CreateResponse,
SearchHit,
SearchResponse,
SortCombinations,
} from '@elastic/elasticsearch/lib/api/types';
import Indices from '@elastic/elasticsearch/lib/api/api/indices.js';
import {CreateResponse, SearchHit, SearchResponse} from '@elastic/elasticsearch/lib/api/types.js';
import {
SCBook,
SCBulkResponse,
@@ -36,27 +31,25 @@ import {expect, use} from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {beforeEach} from 'mocha';
import mockedEnv from 'mocked-env';
import {
ACTIVE_INDICES_ALIAS,
INACTIVE_INDICES_ALIAS,
parseIndexName,
} from '../../../src/storage/elasticsearch/util.js';
import * as queryModule from '../../../src/storage/elasticsearch/query/query.js';
import * as sortModule from '../../../src/storage/elasticsearch/query/sort.js';
import sinon, {SinonStub} from 'sinon';
import {getIndexUID, getThingIndexName, INDEX_UID_LENGTH} from '../../../src/storage/elasticsearch/util.js';
import * as utilModule from '../../../src/storage/elasticsearch/util.js';
import {removeInvalidAliasChars} from '../../../src/storage/elasticsearch/util/alias.js';
import {configFile} from '../../../src/common.js';
import {MailQueue} from '../../../src/notification/mail-queue.js';
import {aggregations} from '../../../src/storage/elasticsearch/templating.js';
import {Elasticsearch} from '../../../src/storage/elasticsearch/elasticsearch.js';
import * as Monitoring from '../../../src/storage/elasticsearch/monitoring.js';
import * as templating from '../../../src/storage/elasticsearch/templating.js';
import {bulk, DEFAULT_TEST_TIMEOUT, getTransport, getIndex} from '../../common.js';
import fs from 'fs';
import {backendConfig} from '../../../src/config.js';
import {readFile} from 'fs/promises';
import {
ACTIVE_INDICES_ALIAS,
getIndexUID,
getThingIndexName,
INACTIVE_INDICES_ALIAS,
INDEX_UID_LENGTH,
parseIndexName,
} from '../../../src/storage/elasticsearch/util/index.js';
import cron from 'node-cron';
import {query} from './query.js';
use(chaiAsPromised);
@@ -115,7 +108,7 @@ describe('Elasticsearch', function () {
describe('getAliasMap', function () {
it('should fail after retries', async function () {
const es = new Elasticsearch(configFile);
const es = new Elasticsearch(backendConfig);
sandbox.stub(es.client.indices, 'getAlias').throws();
await expect(es.init({maxRetries: 1, retryInterval: 10})).to.be.rejected;
});
@@ -283,17 +276,24 @@ describe('Elasticsearch', function () {
...backendConfig.internal,
monitoring: {
actions: [],
watchers: [],
watchers: [
{
triggers: [{executionTime: 'daily', name: 'trigger'}],
name: 'watcher',
actions: [],
query: {},
conditions: [],
},
],
},
},
};
const monitoringSetUpStub = sandbox.stub(Monitoring, 'setUp');
const cronSetupStub = sandbox.stub(cron, 'schedule');
const es = new Elasticsearch(config, new MailQueue(getTransport(false) as unknown as SMTP));
es.init();
expect(monitoringSetUpStub.called).to.be.true;
expect(cronSetupStub.called).to.be.true;
});
});
@@ -308,14 +308,14 @@ describe('Elasticsearch', function () {
beforeEach(function () {
sandbox
.stub(Indices.prototype, 'getAlias')
.stub(Indices.default.prototype, 'getAlias')
.resolves({[oldIndex]: {aliases: {[SCThingType.Book]: {}}}} as any);
sandbox.stub(Indices.prototype, 'putTemplate').resolves({} as any);
createStub = sandbox.stub(Indices.prototype, 'create').resolves({} as any);
deleteStub = sandbox.stub(Indices.prototype, 'delete').resolves({} as any);
refreshStub = sandbox.stub(Indices.prototype, 'refresh').resolves({} as any);
updateAliasesStub = sandbox.stub(Indices.prototype, 'updateAliases').resolves({} as any);
es = new Elasticsearch(configFile);
sandbox.stub(Indices.default.prototype, 'putTemplate').resolves({} as any);
createStub = sandbox.stub(Indices.default.prototype, 'create').resolves({} as any);
deleteStub = sandbox.stub(Indices.default.prototype, 'delete').resolves({} as any);
refreshStub = sandbox.stub(Indices.default.prototype, 'refresh').resolves({} as any);
updateAliasesStub = sandbox.stub(Indices.default.prototype, 'updateAliases').resolves({} as any);
es = new Elasticsearch(backendConfig);
});
afterEach(function () {
@@ -329,21 +329,18 @@ describe('Elasticsearch', function () {
it('should reject (throw an error) if the index name is not valid', async function () {
sandbox.createStubInstance(Client, {});
sandbox.stub(utilModule, 'getThingIndexName').returns(`invalid_${getIndex}`);
const invalidBulk = {...bulk, source: '%#$^'};
await es.init();
return expect(es.bulkCreated(bulk)).to.be.rejectedWith('Index');
return expect(es.bulkCreated(invalidBulk)).to.be.rejectedWith('Index');
});
it('should create a new index', async function () {
const index = getIndex();
sandbox.stub(utilModule, 'getThingIndexName').returns(index);
const putTemplateStub = sandbox.stub(templating, 'putTemplate');
sandbox.stub(es, 'prepareBulkWrite').resolves(index);
await es.init();
await es.bulkCreated(bulk);
expect(putTemplateStub.called).to.be.true;
expect(createStub.calledWith({index, aliases: {[INACTIVE_INDICES_ALIAS]: {}}})).to.be.true;
});
});
@@ -354,7 +351,7 @@ describe('Elasticsearch', function () {
sandbox.restore();
});
it('should cleanup index in case of the expired bulk for bulk whose index is not in use', async function () {
sandbox.stub(utilModule, 'getThingIndexName').returns(getIndex());
sandbox.stub(es, 'prepareBulkWrite').resolves(getIndex());
await es.init();
await es.bulkExpired({...bulk, state: 'in progress'});
@@ -363,7 +360,7 @@ describe('Elasticsearch', function () {
});
it('should not cleanup index in case of the expired bulk for bulk whose index is in use', async function () {
sandbox.stub(utilModule, 'getThingIndexName').returns(getIndex());
sandbox.stub(es, 'prepareBulkWrite').resolves(getIndex());
await es.init();
await es.bulkExpired({...bulk, state: 'done'});
@@ -378,11 +375,11 @@ describe('Elasticsearch', function () {
});
it('should reject if the index name is not valid', async function () {
sandbox.stub(utilModule, 'getThingIndexName').returns(`invalid_${getIndex()}`);
const invalidBulk = {...bulk, source: '%#$^'};
sandbox.createStubInstance(Client, {});
await es.init();
return expect(es.bulkUpdated(bulk)).to.be.rejectedWith('Index');
return expect(es.bulkUpdated(invalidBulk)).to.be.rejectedWith('Index');
});
it("should refuse to finalize bulk if index doesn't exist", async function () {
@@ -411,10 +408,8 @@ describe('Elasticsearch', function () {
remove_index: {index: oldIndex},
},
];
sandbox.stub(utilModule, 'getThingIndexName').returns(index);
sandbox.stub(templating, 'putTemplate');
sandbox.stub(es, 'prepareBulkWrite').resolves(index);
await es.init();
await es.bulkUpdated(bulk);
expect(refreshStub.calledWith({index, allow_no_indices: false})).to.be.true;
@@ -467,7 +462,7 @@ describe('Elasticsearch', function () {
});
beforeEach(function () {
sandbox.stub(Indices.prototype, 'getAlias').resolves({} as any);
sandbox.stub(Indices.default.prototype, 'getAlias').resolves({} as any);
});
afterEach(function () {
@@ -483,7 +478,7 @@ describe('Elasticsearch', function () {
_source: message as SCMessage,
};
sandbox.stub(es.client, 'search').resolves(searchResponse<SCMessage>(object));
sandbox.stub(utilModule, 'getThingIndexName').returns(index);
sandbox.stub(es, 'prepareBulkWrite').resolves(index);
await es.init();
return expect(es.post(object._source!, bulk)).to.be.rejectedWith('UID conflict');
@@ -609,7 +604,7 @@ describe('Elasticsearch', function () {
};
let searchStub: sinon.SinonStub;
before(function () {
es = new Elasticsearch(configFile);
es = new Elasticsearch(backendConfig);
});
beforeEach(function () {
searchStub = sandbox.stub(es.client, 'search').resolves(fakeSearchResponse);
@@ -680,18 +675,14 @@ describe('Elasticsearch', function () {
},
},
};
const fakeResponse = {foo: 'bar'} as SortCombinations;
const fakeBuildSortResponse = [fakeResponse];
// @ts-expect-error not assignable
sandbox.stub(queryModule, 'buildQuery').returns(fakeResponse);
sandbox.stub(sortModule, 'buildSort').returns(fakeBuildSortResponse);
await es.search(parameters);
sandbox.assert.calledWithMatch(searchStub, {
expect(searchStub.firstCall.firstArg).to.be.deep.equal({
aggs: aggregations,
query: fakeResponse,
sort: fakeBuildSortResponse,
query,
allow_no_indices: true,
sort: [{'name.sort': 'desc'}],
from: parameters.from,
index: ACTIVE_INDICES_ALIAS,
size: parameters.size,

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Client} from '@elastic/elasticsearch';
import {SearchResponse} from '@elastic/elasticsearch/lib/api/types';
import {SearchResponse} from '@elastic/elasticsearch/lib/api/types.js';
import {
SCMonitoringConfiguration,
SCMonitoringLogAction,

View File

@@ -31,7 +31,7 @@ import {buildQuery} from '../../../src/storage/elasticsearch/query/query.js';
import {buildSort} from '../../../src/storage/elasticsearch/query/sort.js';
import {ElasticsearchConfig} from '../../../src/storage/elasticsearch/types/elasticsearch-config.js';
import {QueryDslSpecificQueryContainer} from '../../../src/storage/elasticsearch/types/util.js';
import {SortCombinations} from '@elastic/elasticsearch/lib/api/types';
import {SortCombinations} from '@elastic/elasticsearch/lib/api/types.js';
import {backendConfig} from '../../../src/config.js';
describe('Query', function () {

View File

@@ -0,0 +1,386 @@
import {QueryDslQueryContainer} from '@elastic/elasticsearch/lib/api/types.js';
export const query: QueryDslQueryContainer = {
function_score: {
functions: [
{
filter: {
term: {
type: 'academic event',
},
},
weight: 1,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'academic event',
},
},
{
term: {
'academicTerms.acronym.raw': 'SS 2023',
},
},
],
should: [],
},
},
weight: 1.1,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'academic event',
},
},
{
term: {
'academicTerms.acronym.raw': 'WS 2022/23',
},
},
],
should: [],
},
},
weight: 1.05,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'academic event',
},
},
{
term: {
'academicTerms.acronym.raw': 'SoSe 2023',
},
},
],
should: [],
},
},
weight: 1.1,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'academic event',
},
},
{
term: {
'academicTerms.acronym.raw': 'WiSe 2022/23',
},
},
],
should: [],
},
},
weight: 1.05,
},
{
filter: {
term: {
type: 'academic event',
},
},
weight: 1,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'academic event',
},
},
{
term: {
'categories.raw': 'course',
},
},
],
should: [],
},
},
weight: 1.08,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'academic event',
},
},
{
term: {
'categories.raw': 'integrated course',
},
},
],
should: [],
},
},
weight: 1.08,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'academic event',
},
},
{
term: {
'categories.raw': 'introductory class',
},
},
],
should: [],
},
},
weight: 1.05,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'academic event',
},
},
{
term: {
'categories.raw': 'lecture',
},
},
],
should: [],
},
},
weight: 1.1,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'academic event',
},
},
{
term: {
'categories.raw': 'seminar',
},
},
],
should: [],
},
},
weight: 1.01,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'academic event',
},
},
{
term: {
'categories.raw': 'tutorial',
},
},
],
should: [],
},
},
weight: 1.05,
},
{
filter: {
term: {
type: 'building',
},
},
weight: 1.6,
},
{
filter: {
term: {
type: 'point of interest',
},
},
weight: 1,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'point of interest',
},
},
{
term: {
'categories.raw': 'cafe',
},
},
],
should: [],
},
},
weight: 1.1,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'point of interest',
},
},
{
term: {
'categories.raw': 'learn',
},
},
],
should: [],
},
},
weight: 1.1,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'point of interest',
},
},
{
term: {
'categories.raw': 'library',
},
},
],
should: [],
},
},
weight: 1.2,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'point of interest',
},
},
{
term: {
'categories.raw': 'restaurant',
},
},
],
should: [],
},
},
weight: 1.1,
},
{
filter: {
term: {
type: 'dish',
},
},
weight: 1,
},
{
filter: {
bool: {
must: [
{
term: {
type: 'dish',
},
},
{
term: {
'categories.raw': 'main dish',
},
},
],
should: [],
},
},
weight: 2,
},
],
query: {
bool: {
minimum_should_match: 0,
must: [
{
dis_max: {
boost: 1.2,
queries: [
{
match: {
name: {
boost: 1.3,
fuzziness: 'AUTO',
query: 'mathematics',
},
},
},
{
query_string: {
default_field: 'name',
minimum_should_match: '75%',
query: 'mathematics',
},
},
],
tie_breaker: 0,
},
},
{
term: {
'type.raw': 'academic event',
},
},
],
should: [],
},
},
score_mode: 'multiply',
},
};