* Allow submission of null country
* Update searchAssetBuilder to handle nulls
andWhere({country:null}) produces `"exifInfo"."country" = NULL`. We want
`"exifInfo"."country" IS NULL`, so we have to treat NULL as a special
case
* Allow null country in frontend
* Make the query code a bit more straightforward
* Remove unused brackets import
* Remove log message
* Don't change whitespace for no reason
* Fix prettier style issue
* Update search.dto.ts validators per @jrasm91's recommendation
* Update api types
* Combine null country and state into one guard clause
* chore: clean up
* chore: add e2e for null/empty city, state, country search
* refactor: server returns suggestion for null values
* chore: clean up
---------
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: Jason Rasmussen <jason@rasm.me>
118 lines
5.0 KiB
TypeScript
118 lines
5.0 KiB
TypeScript
import { mapAsset } from 'src/dtos/asset-response.dto';
|
|
import { SearchSuggestionType } from 'src/dtos/search.dto';
|
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
|
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
|
|
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
|
|
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
|
import { IPersonRepository } from 'src/interfaces/person.interface';
|
|
import { ISearchRepository } from 'src/interfaces/search.interface';
|
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
|
import { SearchService } from 'src/services/search.service';
|
|
import { assetStub } from 'test/fixtures/asset.stub';
|
|
import { authStub } from 'test/fixtures/auth.stub';
|
|
import { personStub } from 'test/fixtures/person.stub';
|
|
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
|
|
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
|
|
import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock';
|
|
import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock';
|
|
import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock';
|
|
import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
|
|
import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock';
|
|
import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock';
|
|
import { Mocked, beforeEach, vitest } from 'vitest';
|
|
|
|
vitest.useFakeTimers();
|
|
|
|
describe(SearchService.name, () => {
|
|
let sut: SearchService;
|
|
let assetMock: Mocked<IAssetRepository>;
|
|
let systemMock: Mocked<ISystemMetadataRepository>;
|
|
let machineMock: Mocked<IMachineLearningRepository>;
|
|
let personMock: Mocked<IPersonRepository>;
|
|
let searchMock: Mocked<ISearchRepository>;
|
|
let partnerMock: Mocked<IPartnerRepository>;
|
|
let metadataMock: Mocked<IMetadataRepository>;
|
|
let loggerMock: Mocked<ILoggerRepository>;
|
|
|
|
beforeEach(() => {
|
|
assetMock = newAssetRepositoryMock();
|
|
systemMock = newSystemMetadataRepositoryMock();
|
|
machineMock = newMachineLearningRepositoryMock();
|
|
personMock = newPersonRepositoryMock();
|
|
searchMock = newSearchRepositoryMock();
|
|
partnerMock = newPartnerRepositoryMock();
|
|
metadataMock = newMetadataRepositoryMock();
|
|
loggerMock = newLoggerRepositoryMock();
|
|
|
|
sut = new SearchService(
|
|
systemMock,
|
|
machineMock,
|
|
personMock,
|
|
searchMock,
|
|
assetMock,
|
|
partnerMock,
|
|
metadataMock,
|
|
loggerMock,
|
|
);
|
|
});
|
|
|
|
it('should work', () => {
|
|
expect(sut).toBeDefined();
|
|
});
|
|
|
|
describe('searchPerson', () => {
|
|
it('should pass options to search', async () => {
|
|
const { name } = personStub.withName;
|
|
|
|
await sut.searchPerson(authStub.user1, { name, withHidden: false });
|
|
|
|
expect(personMock.getByName).toHaveBeenCalledWith(authStub.user1.user.id, name, { withHidden: false });
|
|
|
|
await sut.searchPerson(authStub.user1, { name, withHidden: true });
|
|
|
|
expect(personMock.getByName).toHaveBeenCalledWith(authStub.user1.user.id, name, { withHidden: true });
|
|
});
|
|
});
|
|
|
|
describe('getExploreData', () => {
|
|
it('should get assets by city and tag', async () => {
|
|
assetMock.getAssetIdByCity.mockResolvedValue({
|
|
fieldName: 'exifInfo.city',
|
|
items: [{ value: 'Paris', data: assetStub.image.id }],
|
|
});
|
|
assetMock.getAssetIdByTag.mockResolvedValue({
|
|
fieldName: 'smartInfo.tags',
|
|
items: [{ value: 'train', data: assetStub.imageFrom2015.id }],
|
|
});
|
|
assetMock.getByIdsWithAllRelations.mockResolvedValue([assetStub.image, assetStub.imageFrom2015]);
|
|
const expectedResponse = [
|
|
{ fieldName: 'exifInfo.city', items: [{ value: 'Paris', data: mapAsset(assetStub.image) }] },
|
|
{ fieldName: 'smartInfo.tags', items: [{ value: 'train', data: mapAsset(assetStub.imageFrom2015) }] },
|
|
];
|
|
|
|
const result = await sut.getExploreData(authStub.user1);
|
|
|
|
expect(result).toEqual(expectedResponse);
|
|
});
|
|
});
|
|
|
|
describe('getSearchSuggestions', () => {
|
|
it('should return search suggestions (including null)', async () => {
|
|
metadataMock.getCountries.mockResolvedValue(['USA', null]);
|
|
await expect(
|
|
sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.COUNTRY }),
|
|
).resolves.toEqual(['USA', null]);
|
|
expect(metadataMock.getCountries).toHaveBeenCalledWith(authStub.user1.user.id);
|
|
});
|
|
|
|
it('should return search suggestions (without null)', async () => {
|
|
metadataMock.getCountries.mockResolvedValue(['USA', null]);
|
|
await expect(
|
|
sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.COUNTRY }),
|
|
).resolves.toEqual(['USA']);
|
|
expect(metadataMock.getCountries).toHaveBeenCalledWith(authStub.user1.user.id);
|
|
});
|
|
});
|
|
});
|