Taichi Masuyama 4 лет назад
Родитель
Сommit
cbffb1491e

+ 28 - 9
packages/app/src/server/service/search.ts

@@ -1,13 +1,12 @@
 import mongoose from 'mongoose';
 import RE2 from 're2';
 
-import { NamedQueryModel, NamedQueryDocument } from '../models/named-query';
+import { NamedQueryModel } from '../models/named-query';
 import {
   SearchDelegator, SearchQueryParser, SearchResolver, ParsedQuery, Result, MetaData, SearchableData, QueryTerms,
 } from '../interfaces/search';
 
 import loggerFactory from '~/utils/logger';
-import { SearchDelegatorName } from '~/interfaces/named-query';
 
 // eslint-disable-next-line no-unused-vars
 const logger = loggerFactory('growi:service:search');
@@ -170,15 +169,16 @@ class SearchService implements SearchQueryParser, SearchResolver {
 
     const name = queryString.replace(replaceRegexp, '');
     const nq = await NamedQuery.findOne({ name });
+
     if (nq == null) {
-      throw Error('Named Query not found.');
+      throw Error('Named query was not found.');
     }
 
     const { aliasOf, delegatorName } = nq;
 
     let parsedQuery;
     if (aliasOf != null) {
-      parsedQuery = { queryString, terms: this.parseQueryString(aliasOf) };
+      parsedQuery = { queryString: normalizeQueryString(aliasOf), terms: this.parseQueryString(aliasOf) };
     }
     if (delegatorName != null) {
       parsedQuery = { queryString, delegatorName };
@@ -189,7 +189,6 @@ class SearchService implements SearchQueryParser, SearchResolver {
 
   async resolve(parsedQuery: ParsedQuery): Promise<[SearchDelegator, SearchableData | null]> {
     const { queryString, terms, delegatorName } = parsedQuery;
-
     if (delegatorName != null) {
       const nqDelegator = this.nqDelegators[delegatorName];
       if (nqDelegator != null) {
@@ -197,19 +196,39 @@ class SearchService implements SearchQueryParser, SearchResolver {
       }
     }
 
+    if (terms == null) {
+      throw Error('Either of terms or delegatorName must not be null');
+    }
+
     const data = {
       queryString,
-      terms: this.parseQueryString(queryString),
+      terms,
     };
     return [this.delegator, data];
   }
 
   async searchKeyword(keyword: string, user, userGroups, searchOpts): Promise<Result<any> & MetaData> {
+    let parsedQuery;
     // parse
-    const parsedQuery = await this.parseSearchQuery(keyword);
+    try {
+      parsedQuery = await this.parseSearchQuery(keyword);
+    }
+    catch (err) {
+      logger.error('Error occurred while parseSearchQuery', err);
+      throw err;
+    }
+
+    let delegator;
+    let data;
     // resolve
-    const [delegator, data] = await this.resolve(parsedQuery);
-    // search
+    try {
+      [delegator, data] = await this.resolve(parsedQuery);
+    }
+    catch (err) {
+      logger.error('Error occurred while resolving search delegator', err);
+      throw err;
+    }
+
     return delegator.search(data, user, userGroups, searchOpts);
   }
 

+ 102 - 6
packages/app/src/test/integration/service/search/search-service.test.js

@@ -1,23 +1,82 @@
 import SearchService from '~/server/service/search';
+import NamedQuery from '~/server/models/named-query';
 
 const { getInstance } = require('../../setup-crowi');
 
 describe('SearchService test', () => {
   let crowi;
   let searchService;
-  const queryString1 = 'match -notmatch "phrase" -"notphrase" [nq:named_query] prefix:/pre1 -prefix:/pre2 tag:Tag1 -tag:Tag2';
+
+  const DEFAULT = 'FullTextSearch';
+
+  // let NamedQuery;
+
+  let dummyAliasOf;
+  let dummyDelegatorName;
+
+  let namedQuery1;
+  let namedQuery2;
 
   beforeAll(async() => {
     crowi = await getInstance();
     searchService = new SearchService(crowi);
+    searchService.nqDelegators = {
+      FullTextSearch: 'function',
+    };
   });
 
 
-  describe('parseSearchQuery()', () => {
+  describe('parseQueryString()', () => {
     test('should parse queryString', async() => {
-      const parsedQuery = await searchService.parseSearchQuery(queryString1);
+      const queryString = 'match -notmatch "phrase" -"notphrase" [nq:named_query] prefix:/pre1 -prefix:/pre2 tag:Tag1 -tag:Tag2';
+      const terms = await searchService.parseQueryString(queryString);
+
+      const expected = { // QueryTerms
+        match: ['match', '[nq:named_query]'],
+        not_match: ['notmatch'],
+        phrase: ['"phrase"'],
+        not_phrase: ['"notphrase"'],
+        prefix: ['/pre1'],
+        not_prefix: ['/pre2'],
+        tag: ['Tag1'],
+        not_tag: ['Tag2'],
+      };
+
+      expect(terms).toStrictEqual(expected);
+    });
+  });
+
+  describe('parseSearchQuery()', () => {
+    beforeAll(async() => {
+      dummyDelegatorName = DEFAULT;
+      dummyAliasOf = 'match -notmatch "phrase" -"notphrase" prefix:/pre1 -prefix:/pre2 tag:Tag1 -tag:Tag2';
+
+      await NamedQuery.insertMany([
+        { name: 'named_query1', delegatorName: dummyDelegatorName },
+        { name: 'named_query2', aliasOf: dummyAliasOf },
+      ]);
+
+      namedQuery1 = await NamedQuery.findOne({ name: 'named_query1' });
+      namedQuery2 = await NamedQuery.findOne({ name: 'named_query2' });
+    });
+
+    test('should return result with delegatorName', async() => {
+      const queryString = '[nq:named_query1]';
+      const parsedQuery = await searchService.parseSearchQuery(queryString);
+
+      const expected = {
+        queryString,
+        delegatorName: dummyDelegatorName,
+      };
+
+      expect(parsedQuery).toStrictEqual(expected);
+    });
+
+    test('should return result with expanded aliasOf value', async() => {
+      const queryString = '[nq:named_query2]';
+      const parsedQuery = await searchService.parseSearchQuery(queryString);
       const expected = {
-        queryString: queryString1,
+        queryString: dummyAliasOf,
         terms: {
           match: ['match'],
           not_match: ['notmatch'],
@@ -28,12 +87,49 @@ describe('SearchService test', () => {
           tag: ['Tag1'],
           not_tag: ['Tag2'],
         },
-        nqNames: ['named_query'],
       };
 
       expect(parsedQuery).toStrictEqual(expected);
     });
-  });
 
+    test('should resolve as full-text search delegator', async() => {
+      const parsedQuery = {
+        queryString: dummyAliasOf,
+        terms: {
+          match: ['match'],
+          not_match: ['notmatch'],
+          phrase: ['"phrase"'],
+          not_phrase: ['"notphrase"'],
+          prefix: ['/pre1'],
+          not_prefix: ['/pre2'],
+          tag: ['Tag1'],
+          not_tag: ['Tag2'],
+        },
+      };
+
+      const [delegator, data] = await searchService.resolve(parsedQuery);
+
+      const expectedData = parsedQuery;
+
+      expect(data).toStrictEqual(expectedData);
+      expect(typeof delegator.search).toBe('function');
+    });
+
+    test('should resolve as custom search delegator', async() => {
+      const queryString = '[nq:named_query1]';
+      const parsedQuery = {
+        queryString,
+        delegatorName: dummyDelegatorName,
+      };
+
+      const [delegator, data] = await searchService.resolve(parsedQuery);
+
+      const expectedData = null;
+
+      expect(data).toBe(expectedData);
+      expect(typeof delegator.search).toBe('function');
+    });
+
+  });
 
 });