diff --git a/.travis.yml b/.travis.yml index 69c0851..2203150 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,15 @@ dist: trusty language: node_js node_js: -- '10' -- '11' - + - '10' + - '11' +env: + - Server-database-mysql-host='localhost' + - Server-database-mysql-username='root' + - Server-database-mysql-password='' + - Server-database-mysql-database='pigallery2_travis' +services: + - mysql addons: chrome: stable before_install: diff --git a/backend/middlewares/GalleryMWs.ts b/backend/middlewares/GalleryMWs.ts index 80f5fe3..61154d1 100644 --- a/backend/middlewares/GalleryMWs.ts +++ b/backend/middlewares/GalleryMWs.ts @@ -222,7 +222,7 @@ export class GalleryMWs { } public static async autocomplete(req: Request, res: Response, next: NextFunction) { - if (Config.Client.Search.autocompleteEnabled === false) { + if (Config.Client.Search.AutoComplete.enabled === false) { return next(); } if (!(req.params.text)) { diff --git a/backend/model/sql/SQLConnection.ts b/backend/model/sql/SQLConnection.ts index 22a8fee..4ae051a 100644 --- a/backend/model/sql/SQLConnection.ts +++ b/backend/model/sql/SQLConnection.ts @@ -17,16 +17,17 @@ import {DataStructureVersion} from '../../../common/DataStructureVersion'; import {FileEntity} from './enitites/FileEntity'; import {FaceRegionEntry} from './enitites/FaceRegionEntry'; import {PersonEntry} from './enitites/PersonEntry'; +import {Utils} from '../../../common/Utils'; export class SQLConnection { + private static connection: Connection = null; + constructor() { } - private static connection: Connection = null; - public static async getConnection(): Promise { if (this.connection == null) { const options: any = this.getDriver(Config.Server.database); @@ -44,8 +45,10 @@ export class SQLConnection { VersionEntity ]; options.synchronize = false; - //options.logging = 'all'; - this.connection = await createConnection(options); + // options.logging = 'all'; + + + this.connection = await this.createConnection(options); await SQLConnection.schemeSync(this.connection); } return this.connection; @@ -72,7 +75,7 @@ export class SQLConnection { ]; options.synchronize = false; // options.logging = "all"; - const conn = await createConnection(options); + const conn = await this.createConnection(options); await SQLConnection.schemeSync(conn); await conn.close(); return true; @@ -92,6 +95,38 @@ export class SQLConnection { } + public static async close() { + try { + if (this.connection != null) { + await this.connection.close(); + this.connection = null; + } + } catch (err) { + console.error(err); + } + } + + private static async createConnection(options: ConnectionOptions) { + if (options.type === 'sqlite') { + return await createConnection(options); + } + try { + return await createConnection(options); + } catch (e) { + if (e.sqlMessage === 'Unknown database \'' + options.database + '\'') { + Logger.debug('creating database: ' + options.database); + const tmpOption = Utils.clone(options); + // @ts-ignore + delete tmpOption.database; + const tmpConn = await createConnection(tmpOption); + await tmpConn.query('CREATE DATABASE IF NOT EXISTS ' + options.database); + await tmpConn.close(); + return await createConnection(options); + } + throw e; + } + } + private static async schemeSync(connection: Connection) { let version = null; try { @@ -145,16 +180,5 @@ export class SQLConnection { return driver; } - public static async close() { - try { - if (this.connection != null) { - await this.connection.close(); - this.connection = null; - } - } catch (err) { - console.error(err); - } - } - } diff --git a/backend/model/sql/SearchManager.ts b/backend/model/sql/SearchManager.ts index f6c06fa..12d7650 100644 --- a/backend/model/sql/SearchManager.ts +++ b/backend/model/sql/SearchManager.ts @@ -9,6 +9,7 @@ import {VideoEntity} from './enitites/VideoEntity'; import {PersonEntry} from './enitites/PersonEntry'; import {FaceRegionEntry} from './enitites/FaceRegionEntry'; import {SelectQueryBuilder} from 'typeorm'; +import {Config} from '../../../common/config/private/Config'; export class SearchManager implements ISearchManager { @@ -40,7 +41,7 @@ export class SearchManager implements ISearchManager { .createQueryBuilder('photo') .select('DISTINCT(photo.metadata.keywords)') .where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) - .limit(5) + .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) .getRawMany()) .map(r => >(r.metadataKeywords).split(',')) .forEach(keywords => { @@ -52,7 +53,8 @@ export class SearchManager implements ISearchManager { .createQueryBuilder('person') .select('DISTINCT(person.name)') .where('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) - .limit(5) + .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) + .orderBy('person.name') .getRawMany()) .map(r => r.name), SearchTypes.person)); @@ -64,7 +66,7 @@ export class SearchManager implements ISearchManager { .orWhere('photo.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .orWhere('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .groupBy('photo.metadata.positionData.country, photo.metadata.positionData.state, photo.metadata.positionData.city') - .limit(5) + .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) .getRawMany()) .filter(pm => !!pm) .map(pm => >[pm.city || '', pm.country || '', pm.state || '']) @@ -77,7 +79,7 @@ export class SearchManager implements ISearchManager { .createQueryBuilder('media') .select('DISTINCT(media.name)') .where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) - .limit(5) + .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) .getRawMany()) .map(r => r.name), SearchTypes.photo)); @@ -86,7 +88,7 @@ export class SearchManager implements ISearchManager { .createQueryBuilder('media') .select('DISTINCT(media.metadata.caption) as caption') .where('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) - .limit(5) + .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) .getRawMany()) .map(r => r.caption), SearchTypes.photo)); @@ -95,7 +97,7 @@ export class SearchManager implements ISearchManager { .createQueryBuilder('media') .select('DISTINCT(media.name)') .where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) - .limit(5) + .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) .getRawMany()) .map(r => r.name), SearchTypes.video)); @@ -103,7 +105,7 @@ export class SearchManager implements ISearchManager { .createQueryBuilder('dir') .select('DISTINCT(dir.name)') .where('dir.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) - .limit(5) + .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory) .getRawMany()) .map(r => r.name), SearchTypes.directory)); diff --git a/backend/model/sql/enitites/DirectoryEntity.ts b/backend/model/sql/enitites/DirectoryEntity.ts index e43c633..717b53c 100644 --- a/backend/model/sql/enitites/DirectoryEntity.ts +++ b/backend/model/sql/enitites/DirectoryEntity.ts @@ -1,4 +1,4 @@ -import {Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique, Index} from 'typeorm'; +import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique} from 'typeorm'; import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO'; import {MediaEntity} from './MediaEntity'; import {FileEntity} from './FileEntity'; @@ -8,7 +8,7 @@ import {FileEntity} from './FileEntity'; export class DirectoryEntity implements DirectoryDTO { @Index() - @PrimaryGeneratedColumn() + @PrimaryGeneratedColumn({unsigned: true}) id: number; @Index() @@ -22,18 +22,28 @@ export class DirectoryEntity implements DirectoryDTO { /** * last time the directory was modified (from outside, eg.: a new media was added) */ - @Column('bigint') + @Column('bigint', { + unsigned: true, transformer: { + from: v => parseInt(v, 10), + to: v => v + } + }) public lastModified: number; /** * Last time the directory was fully scanned, not only for a few media to create a preview */ - @Column({type: 'bigint', nullable: true}) + @Column({ + type: 'bigint', nullable: true, unsigned: true, transformer: { + from: v => parseInt(v, 10), + to: v => v + } + }) public lastScanned: number; isPartial?: boolean; - @Column('smallint') + @Column('smallint', {unsigned: true}) mediaCount: number; @Index() diff --git a/backend/model/sql/enitites/FaceRegionEntry.ts b/backend/model/sql/enitites/FaceRegionEntry.ts index 3b569a5..411be7f 100644 --- a/backend/model/sql/enitites/FaceRegionEntry.ts +++ b/backend/model/sql/enitites/FaceRegionEntry.ts @@ -20,7 +20,7 @@ export class FaceRegionBoxEntry implements FaceRegionBox { @Entity() export class FaceRegionEntry { - @PrimaryGeneratedColumn() + @PrimaryGeneratedColumn({unsigned: true}) id: number; @Column(type => FaceRegionBoxEntry) diff --git a/backend/model/sql/enitites/FileEntity.ts b/backend/model/sql/enitites/FileEntity.ts index 0d19efd..b185cfe 100644 --- a/backend/model/sql/enitites/FileEntity.ts +++ b/backend/model/sql/enitites/FileEntity.ts @@ -7,7 +7,7 @@ import {FileDTO} from '../../../../common/entities/FileDTO'; export class FileEntity implements FileDTO { @Index() - @PrimaryGeneratedColumn() + @PrimaryGeneratedColumn({unsigned: true}) id: number; @Column('text') diff --git a/backend/model/sql/enitites/MediaEntity.ts b/backend/model/sql/enitites/MediaEntity.ts index 026290d..10d3693 100644 --- a/backend/model/sql/enitites/MediaEntity.ts +++ b/backend/model/sql/enitites/MediaEntity.ts @@ -1,4 +1,4 @@ -import {Column, Entity, OneToMany, ManyToOne, PrimaryGeneratedColumn, TableInheritance, Unique, Index} from 'typeorm'; +import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance, Unique} from 'typeorm'; import {DirectoryEntity} from './DirectoryEntity'; import {MediaDimension, MediaDTO, MediaMetadata} from '../../../../common/entities/MediaDTO'; import {OrientationTypes} from 'ts-exif-parser'; @@ -22,10 +22,15 @@ export class MediaMetadataEntity implements MediaMetadata { @Column(type => MediaDimensionEntity) size: MediaDimensionEntity; - @Column('bigint') + @Column('bigint', { + unsigned: true, transformer: { + from: v => parseInt(v, 10), + to: v => v + } + }) creationDate: number; - @Column('int') + @Column('int', {unsigned: true}) fileSize: number; @Column('simple-array') @@ -37,30 +42,30 @@ export class MediaMetadataEntity implements MediaMetadata { @Column(type => PositionMetaDataEntity) positionData: PositionMetaDataEntity; - @Column('tinyint', {default: OrientationTypes.TOP_LEFT}) + @Column('tinyint', {unsigned: true, default: OrientationTypes.TOP_LEFT}) orientation: OrientationTypes; @OneToMany(type => FaceRegionEntry, faceRegion => faceRegion.media) faces: FaceRegionEntry[]; - @Column('int') + @Column('int', {unsigned: true}) bitRate: number; - @Column('bigint') + @Column('int', {unsigned: true}) duration: number; } // TODO: fix inheritance once its working in typeorm @Entity() @Unique(['name', 'directory']) -@TableInheritance({column: {type: 'varchar', name: 'type'}}) +@TableInheritance({column: {type: 'varchar', name: 'type', length: 32}}) export abstract class MediaEntity implements MediaDTO { @Index() - @PrimaryGeneratedColumn() + @PrimaryGeneratedColumn({unsigned: true}) id: number; - @Column('text') + @Column() name: string; @Index() diff --git a/backend/model/sql/enitites/PersonEntry.ts b/backend/model/sql/enitites/PersonEntry.ts index d539bd5..2826453 100644 --- a/backend/model/sql/enitites/PersonEntry.ts +++ b/backend/model/sql/enitites/PersonEntry.ts @@ -6,7 +6,7 @@ import {FaceRegionEntry} from './FaceRegionEntry'; @Unique(['name']) export class PersonEntry { @Index() - @PrimaryGeneratedColumn() + @PrimaryGeneratedColumn({unsigned: true}) id: number; @Column() diff --git a/backend/model/sql/enitites/SharingEntity.ts b/backend/model/sql/enitites/SharingEntity.ts index 2031235..7af3d2b 100644 --- a/backend/model/sql/enitites/SharingEntity.ts +++ b/backend/model/sql/enitites/SharingEntity.ts @@ -5,7 +5,7 @@ import {UserDTO} from '../../../../common/entities/UserDTO'; @Entity() export class SharingEntity implements SharingDTO { - @PrimaryGeneratedColumn() + @PrimaryGeneratedColumn({unsigned: true}) id: number; @Column() @@ -17,10 +17,20 @@ export class SharingEntity implements SharingDTO { @Column({type: 'text', nullable: true}) password: string; - @Column() + @Column('bigint', { + unsigned: true, transformer: { + from: v => parseInt(v, 10), + to: v => v + } + }) expires: number; - @Column() + @Column('bigint', { + unsigned: true, transformer: { + from: v => parseInt(v, 10), + to: v => v + } + }) timeStamp: number; @Column() diff --git a/common/DataStructureVersion.ts b/common/DataStructureVersion.ts index 5611455..950d63a 100644 --- a/common/DataStructureVersion.ts +++ b/common/DataStructureVersion.ts @@ -1 +1 @@ -export const DataStructureVersion = 8; +export const DataStructureVersion = 9; diff --git a/common/config/public/ConfigClass.ts b/common/config/public/ConfigClass.ts index 16761a5..cce0061 100644 --- a/common/config/public/ConfigClass.ts +++ b/common/config/public/ConfigClass.ts @@ -7,14 +7,19 @@ export module ClientConfig { OpenStreetMap, Mapbox, Custom } + export interface AutoCompleteConfig { + enabled: boolean; + maxItemsPerCategory: number; + cacheTimeout: number; + } + export interface SearchConfig { enabled: boolean; instantSearchEnabled: boolean; - autocompleteEnabled: boolean; InstantSearchTimeout: number; - autocompleteCacheTimeout: number; instantSearchCacheTimeout: number; searchCacheTimeout: number; + AutoComplete: AutoCompleteConfig; } export interface SharingConfig { @@ -95,11 +100,14 @@ export class PublicConfigClass { Search: { enabled: true, instantSearchEnabled: true, - autocompleteEnabled: true, InstantSearchTimeout: 3000, - autocompleteCacheTimeout: 1000 * 60 * 60, searchCacheTimeout: 1000 * 60 * 60, - instantSearchCacheTimeout: 1000 * 60 * 60 + instantSearchCacheTimeout: 1000 * 60 * 60, + AutoComplete: { + enabled: true, + cacheTimeout: 1000 * 60 * 60, + maxItemsPerCategory: 5 + } }, Sharing: { enabled: true, diff --git a/frontend/app/gallery/cache.gallery.service.ts b/frontend/app/gallery/cache.gallery.service.ts index 7e39f5e..cf50707 100644 --- a/frontend/app/gallery/cache.gallery.service.ts +++ b/frontend/app/gallery/cache.gallery.service.ts @@ -77,7 +77,7 @@ export class GalleryCacheService { const tmp = localStorage.getItem(key); if (tmp != null) { const value: CacheItem = JSON.parse(tmp); - if (value.timestamp < Date.now() - Config.Client.Search.autocompleteCacheTimeout) { + if (value.timestamp < Date.now() - Config.Client.Search.AutoComplete.cacheTimeout) { localStorage.removeItem(key); return null; } diff --git a/frontend/app/gallery/search/search.gallery.component.ts b/frontend/app/gallery/search/search.gallery.component.ts index ea5d620..cc2645b 100644 --- a/frontend/app/gallery/search/search.gallery.component.ts +++ b/frontend/app/gallery/search/search.gallery.component.ts @@ -54,7 +54,7 @@ export class GallerySearchComponent implements OnDestroy { const searchText = (event.target).value.trim(); - if (Config.Client.Search.autocompleteEnabled && + if (Config.Client.Search.AutoComplete.enabled && this.cache.lastAutocomplete !== searchText) { this.cache.lastAutocomplete = searchText; this.autocomplete(searchText).catch(console.error); @@ -92,7 +92,7 @@ export class GallerySearchComponent implements OnDestroy { } private async autocomplete(searchText: string) { - if (!Config.Client.Search.autocompleteEnabled) { + if (!Config.Client.Search.AutoComplete.enabled) { return; } if (searchText.trim() === '.') { diff --git a/frontend/app/settings/settings.service.ts b/frontend/app/settings/settings.service.ts index 7cecaf1..8a3b37a 100644 --- a/frontend/app/settings/settings.service.ts +++ b/frontend/app/settings/settings.service.ts @@ -15,12 +15,15 @@ export class SettingsService { Client: { Search: { enabled: true, - autocompleteEnabled: true, + AutoComplete: { + enabled: true, + cacheTimeout: 1000 * 60 * 60, + maxItemsPerCategory: 5 + }, instantSearchEnabled: true, InstantSearchTimeout: 0, searchCacheTimeout: 1000 * 60 * 60, instantSearchCacheTimeout: 1000 * 60 * 60, - autocompleteCacheTimeout: 1000 * 60 * 60 }, Thumbnail: { concurrentThumbnailGenerations: null, diff --git a/test/backend/SQLTestHelper.ts b/test/backend/SQLTestHelper.ts new file mode 100644 index 0000000..147ed1a --- /dev/null +++ b/test/backend/SQLTestHelper.ts @@ -0,0 +1,107 @@ +import {Config} from '../../common/config/private/Config'; +import {DatabaseType} from '../../common/config/private/IPrivateConfig'; +import * as fs from 'fs'; +import * as path from 'path'; +import {SQLConnection} from '../../backend/model/sql/SQLConnection'; + +declare let describe: any; +const savedDescribe = describe; + +export class SQLTestHelper { + + static enable = { + sqlite: true, + mysql: true + }; + public static readonly savedDescribe = savedDescribe; + tempDir: string; + dbPath: string; + + constructor(public dbType: DatabaseType) { + this.tempDir = path.resolve(__dirname, './tmp'); + this.dbPath = path.resolve(__dirname, './tmp', 'test.db'); + + } + + static describe(name: string, tests: (helper?: SQLTestHelper) => void) { + savedDescribe(name, async () => { + if (SQLTestHelper.enable.sqlite) { + const helper = new SQLTestHelper(DatabaseType.sqlite); + savedDescribe('sqlite', () => { + return tests(helper); + }); + } + if (SQLTestHelper.enable.mysql) { + const helper = new SQLTestHelper(DatabaseType.mysql); + savedDescribe('mysql', function () { + this.timeout(99999999); + // @ts-ignore + return tests(helper); + }); + } + }); + } + + public async initDB() { + if (this.dbType === DatabaseType.sqlite) { + await this.initSQLite(); + } else { + await this.initMySQL(); + } + } + + + public async clearDB() { + if (this.dbType === DatabaseType.sqlite) { + await this.clearUpSQLite(); + } else { + await this.clearUpMysql(); + } + } + + private async initSQLite() { + await this.resetSQLite(); + + Config.Server.database.type = DatabaseType.sqlite; + Config.Server.database.sqlite.storage = this.dbPath; + } + + private async initMySQL() { + Config.Server.database.type = DatabaseType.mysql; + Config.Server.database.mysql.database = 'pigallery2_test'; + + await this.resetMySQL(); + } + + private async resetSQLite() { + await SQLConnection.close(); + + if (fs.existsSync(this.dbPath)) { + fs.unlinkSync(this.dbPath); + } + if (fs.existsSync(this.tempDir)) { + fs.rmdirSync(this.tempDir); + } + } + + private async resetMySQL() { + Config.Server.database.type = DatabaseType.mysql; + Config.Server.database.mysql.database = 'pigallery2_test'; + const conn = await SQLConnection.getConnection(); + await conn.query('DROP DATABASE IF EXISTS ' + conn.options.database); + await conn.query('CREATE DATABASE IF NOT EXISTS ' + conn.options.database); + await SQLConnection.close(); + } + + private async clearUpMysql() { + Config.Server.database.type = DatabaseType.mysql; + Config.Server.database.mysql.database = 'pigallery2_test'; + const conn = await SQLConnection.getConnection(); + await conn.query('DROP DATABASE IF EXISTS ' + conn.options.database); + await SQLConnection.close(); + } + + private async clearUpSQLite() { + return this.resetSQLite(); + } +} diff --git a/test/backend/integration/model/sql/typeorm.ts b/test/backend/integration/model/sql/typeorm.ts index 679fb1b..c159efc 100644 --- a/test/backend/integration/model/sql/typeorm.ts +++ b/test/backend/integration/model/sql/typeorm.ts @@ -16,7 +16,6 @@ import { PositionMetaDataEntity } from '../../../../../backend/model/sql/enitites/PhotoEntity'; import {MediaDimensionEntity} from '../../../../../backend/model/sql/enitites/MediaEntity'; -import {DataStructureVersion} from '../../../../../common/DataStructureVersion'; import {VersionEntity} from '../../../../../backend/model/sql/enitites/VersionEntity'; describe('Typeorm integration', () => { diff --git a/test/backend/unit/model/sql/GalleryManager.ts b/test/backend/unit/model/sql/GalleryManager.ts new file mode 100644 index 0000000..6774d71 --- /dev/null +++ b/test/backend/unit/model/sql/GalleryManager.ts @@ -0,0 +1,58 @@ +import {expect} from 'chai'; +import {TestHelper} from './TestHelper'; +import {SQLTestHelper} from '../../../SQLTestHelper'; +import {GalleryManager} from '../../../../../backend/model/sql/GalleryManager'; +import {IndexingManager} from '../../../../../backend/model/sql/IndexingManager'; +import {DirectoryDTO} from '../../../../../common/entities/DirectoryDTO'; +import {Utils} from '../../../../../common/Utils'; +import {ObjectManagerRepository} from '../../../../../backend/model/ObjectManagerRepository'; +import {PersonManager} from '../../../../../backend/model/sql/PersonManager'; +import {MediaEntity} from '../../../../../backend/model/sql/enitites/MediaEntity'; + +class IndexingManagerTest extends IndexingManager { + + public async saveToDB(scannedDirectory: DirectoryDTO): Promise { + return super.saveToDB(scannedDirectory); + } +} + +// to help WebStorm to handle the test cases +declare let describe: any; +declare const after: any; +describe = SQLTestHelper.describe; + +describe('GalleryManager', (sqlHelper: SQLTestHelper) => { + + + beforeEach(async () => { + await sqlHelper.initDB(); + ObjectManagerRepository.getInstance().PersonManager = new PersonManager(); + }); + + + after(async () => { + await sqlHelper.clearDB(); + }); + + it('should get random photo', async () => { + const gm = new GalleryManager(); + const im = new IndexingManagerTest(); + + const parent = TestHelper.getRandomizedDirectoryEntry(); + const p1 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo1'); + expect(await gm.getRandomPhoto({})).to.not.exist; + DirectoryDTO.removeReferences(parent); + await im.saveToDB(Utils.clone(parent)); + + delete p1.metadata.faces; + delete p1.directory; + delete p1.id; + const found: MediaEntity = await gm.getRandomPhoto({}); + delete found.metadata.bitRate; + delete found.metadata.duration; + delete found.directory; + delete found.id; + expect(Utils.clone(found)).to.be.deep.equal(Utils.clone(p1)); + }); + +}); diff --git a/test/backend/unit/model/sql/IndexingManager.ts b/test/backend/unit/model/sql/IndexingManager.ts index 349283a..1467e85 100644 --- a/test/backend/unit/model/sql/IndexingManager.ts +++ b/test/backend/unit/model/sql/IndexingManager.ts @@ -1,8 +1,7 @@ import {expect} from 'chai'; import * as fs from 'fs'; -import * as path from 'path'; import {Config} from '../../../../../common/config/private/Config'; -import {DatabaseType, ReIndexingSensitivity} from '../../../../../common/config/private/IPrivateConfig'; +import {ReIndexingSensitivity} from '../../../../../common/config/private/IPrivateConfig'; import {SQLConnection} from '../../../../../backend/model/sql/SQLConnection'; import {GalleryManager} from '../../../../../backend/model/sql/GalleryManager'; import {DirectoryDTO} from '../../../../../common/entities/DirectoryDTO'; @@ -15,6 +14,7 @@ import {FileDTO} from '../../../../../common/entities/FileDTO'; import {IndexingManager} from '../../../../../backend/model/sql/IndexingManager'; import {ObjectManagerRepository} from '../../../../../backend/model/ObjectManagerRepository'; import {PersonManager} from '../../../../../backend/model/sql/PersonManager'; +import {SQLTestHelper} from '../../../SQLTestHelper'; class GalleryManagerTest extends GalleryManager { @@ -41,43 +41,22 @@ class IndexingManagerTest extends IndexingManager { } } -describe('IndexingManager', () => { +// to help WebStorm to handle the test cases +declare let describe: any; +declare const after: any; +describe = SQLTestHelper.describe; +describe('IndexingManager', (sqlHelper: SQLTestHelper) => { - const tempDir = path.join(__dirname, '../../tmp'); - const dbPath = path.join(tempDir, 'test.db'); - - - const setUpSqlDB = async () => { - if (fs.existsSync(dbPath)) { - fs.unlinkSync(dbPath); - } - if (!fs.existsSync(tempDir)) { - fs.mkdirSync(tempDir); - } - - Config.Server.database.type = DatabaseType.sqlite; - Config.Server.database.sqlite.storage = dbPath; - ObjectManagerRepository.getInstance().PersonManager = new PersonManager(); - - }; - - const tearDownSqlDB = async () => { - await SQLConnection.close(); - if (fs.existsSync(dbPath)) { - fs.unlinkSync(dbPath); - } - if (fs.existsSync(tempDir)) { - fs.rmdirSync(tempDir); - } - }; beforeEach(async () => { - await setUpSqlDB(); + await sqlHelper.initDB(); + ObjectManagerRepository.getInstance().PersonManager = new PersonManager(); }); - afterEach(async () => { - await tearDownSqlDB(); + + after(async () => { + await sqlHelper.clearDB(); }); const removeIds = (dir: DirectoryDTO) => { @@ -245,7 +224,7 @@ describe('IndexingManager', () => { expect(selected.media.length).to.deep.equal(subDir.media.length); }) as any).timeout(40000); - describe('Test listDirectory', () => { + SQLTestHelper.savedDescribe('Test listDirectory', () => { const statSync = fs.statSync; let dirTime = 0; const indexedTime = { diff --git a/test/backend/unit/model/sql/SearchManager.ts b/test/backend/unit/model/sql/SearchManager.ts index 671d758..a92c362 100644 --- a/test/backend/unit/model/sql/SearchManager.ts +++ b/test/backend/unit/model/sql/SearchManager.ts @@ -1,8 +1,4 @@ import {expect} from 'chai'; -import * as fs from 'fs'; -import * as path from 'path'; -import {Config} from '../../../../../common/config/private/Config'; -import {DatabaseType} from '../../../../../common/config/private/IPrivateConfig'; import {SQLConnection} from '../../../../../backend/model/sql/SQLConnection'; import {PhotoEntity} from '../../../../../backend/model/sql/enitites/PhotoEntity'; import {SearchManager} from '../../../../../backend/model/sql/SearchManager'; @@ -15,12 +11,15 @@ import {VideoEntity} from '../../../../../backend/model/sql/enitites/VideoEntity import {PersonEntry} from '../../../../../backend/model/sql/enitites/PersonEntry'; import {FaceRegionEntry} from '../../../../../backend/model/sql/enitites/FaceRegionEntry'; import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; +import {SQLTestHelper} from '../../../SQLTestHelper'; +import {Config} from '../../../../../common/config/private/Config'; -describe('SearchManager', () => { +// to help WebStorm to handle the test cases +declare let describe: any; +declare const after: any; +describe = SQLTestHelper.describe; - - const tempDir = path.join(__dirname, '../../tmp'); - const dbPath = path.join(tempDir, 'test.db'); +describe('SearchManager', (sqlHelper: SQLTestHelper) => { const dir = TestHelper.getDirectoryEntry(); const p = TestHelper.getPhotoEntry1(dir); @@ -31,15 +30,7 @@ describe('SearchManager', () => { const v = TestHelper.getVideoEntry1(dir); const setUpSqlDB = async () => { - if (fs.existsSync(dbPath)) { - fs.unlinkSync(dbPath); - } - if (!fs.existsSync(tempDir)) { - fs.mkdirSync(tempDir); - } - - Config.Server.database.type = DatabaseType.sqlite; - Config.Server.database.sqlite.storage = dbPath; + await sqlHelper.initDB(); const savePhoto = async (photo: PhotoDTO) => { const savedPhoto = await pr.save(photo); @@ -66,24 +57,15 @@ describe('SearchManager', () => { await SQLConnection.close(); }; - const tearDownSqlDB = async () => { - await SQLConnection.close(); - if (fs.existsSync(dbPath)) { - fs.unlinkSync(dbPath); - } - if (fs.existsSync(tempDir)) { - fs.rmdirSync(tempDir); - } - }; beforeEach(async () => { await setUpSqlDB(); }); - afterEach(async () => { - await tearDownSqlDB(); - }); + after(async () => { + await sqlHelper.clearDB(); + }); it('should get autocomplete', async () => { const sm = new SearchManager(); @@ -103,6 +85,8 @@ describe('SearchManager', () => { new AutoCompleteItem('wars dir', SearchTypes.directory)]); expect((await sm.autocomplete('arch'))).eql([new AutoCompleteItem('Research City', SearchTypes.position)]); + + Config.Client.Search.AutoComplete.maxItemsPerCategory = 99999; expect((await sm.autocomplete('a')).sort(cmp)).eql([ new AutoCompleteItem('Boba Fett', SearchTypes.keyword), new AutoCompleteItem('Boba Fett', SearchTypes.person), @@ -113,6 +97,7 @@ describe('SearchManager', () => { new AutoCompleteItem('Han Solo', SearchTypes.person), new AutoCompleteItem('death star', SearchTypes.keyword), new AutoCompleteItem('Padmé Amidala', SearchTypes.person), + new AutoCompleteItem('Obivan Kenobi', SearchTypes.person), new AutoCompleteItem('Padmé Amidala', SearchTypes.keyword), new AutoCompleteItem('Natalie Portman', SearchTypes.keyword), new AutoCompleteItem('Han Solo\'s dice', SearchTypes.photo), @@ -121,10 +106,24 @@ describe('SearchManager', () => { new AutoCompleteItem('wars dir', SearchTypes.directory), new AutoCompleteItem('Research City', SearchTypes.position)].sort(cmp)); + Config.Client.Search.AutoComplete.maxItemsPerCategory = 1; + expect((await sm.autocomplete('a')).sort(cmp)).eql([ + new AutoCompleteItem('Anakin', SearchTypes.keyword), + new AutoCompleteItem('star wars', SearchTypes.keyword), + new AutoCompleteItem('death star', SearchTypes.keyword), + new AutoCompleteItem('Anakin Skywalker', SearchTypes.person), + new AutoCompleteItem('Han Solo\'s dice', SearchTypes.photo), + new AutoCompleteItem('Kamino', SearchTypes.position), + new AutoCompleteItem('Research City', SearchTypes.position), + new AutoCompleteItem('wars dir', SearchTypes.directory), + new AutoCompleteItem('Boba Fett', SearchTypes.keyword)].sort(cmp)); + Config.Client.Search.AutoComplete.maxItemsPerCategory = 5; + expect((await sm.autocomplete('sw')).sort(cmp)).to.deep.equal([new AutoCompleteItem('sw1', SearchTypes.photo), new AutoCompleteItem('sw2', SearchTypes.photo), new AutoCompleteItem(v.name, SearchTypes.video)].sort(cmp)); expect((await sm.autocomplete(v.name)).sort(cmp)).to.deep.equal([new AutoCompleteItem(v.name, SearchTypes.video)]); + }); diff --git a/test/backend/unit/model/sql/SharingManager.ts b/test/backend/unit/model/sql/SharingManager.ts index d11b72b..c866a4a 100644 --- a/test/backend/unit/model/sql/SharingManager.ts +++ b/test/backend/unit/model/sql/SharingManager.ts @@ -1,32 +1,23 @@ import {expect} from 'chai'; -import * as fs from 'fs'; -import * as path from 'path'; -import {Config} from '../../../../../common/config/private/Config'; -import {DatabaseType} from '../../../../../common/config/private/IPrivateConfig'; import {SQLConnection} from '../../../../../backend/model/sql/SQLConnection'; import {SharingManager} from '../../../../../backend/model/sql/SharingManager'; import {SharingDTO} from '../../../../../common/entities/SharingDTO'; import {UserEntity} from '../../../../../backend/model/sql/enitites/UserEntity'; import {UserDTO, UserRoles} from '../../../../../common/entities/UserDTO'; +import {SQLTestHelper} from '../../../SQLTestHelper'; -describe('SharingManager', () => { +// to help WebStorm to handle the test cases +declare let describe: any; +declare const after: any; +describe = SQLTestHelper.describe; +describe('SharingManager', (sqlHelper: SQLTestHelper) => { - const tempDir = path.join(__dirname, '../../tmp'); - const dbPath = path.join(tempDir, 'test.db'); let creator: UserDTO = null; const setUpSqlDB = async () => { - if (fs.existsSync(dbPath)) { - fs.unlinkSync(dbPath); - } - if (!fs.existsSync(tempDir)) { - fs.mkdirSync(tempDir); - } - - Config.Server.database.type = DatabaseType.sqlite; - Config.Server.database.sqlite.storage = dbPath; + await sqlHelper.initDB(); const conn = await SQLConnection.getConnection(); @@ -41,22 +32,13 @@ describe('SharingManager', () => { await SQLConnection.close(); }; - const teardownUpSqlDB = async () => { - await SQLConnection.close(); - if (fs.existsSync(dbPath)) { - fs.unlinkSync(dbPath); - } - if (fs.existsSync(tempDir)) { - fs.rmdirSync(tempDir); - } - }; beforeEach(async () => { await setUpSqlDB(); }); - afterEach(async () => { - await teardownUpSqlDB(); + after(async () => { + await sqlHelper.clearDB(); }); diff --git a/test/backend/unit/model/sql/TestHelper.ts b/test/backend/unit/model/sql/TestHelper.ts index bc9f685..05c985e 100644 --- a/test/backend/unit/model/sql/TestHelper.ts +++ b/test/backend/unit/model/sql/TestHelper.ts @@ -258,7 +258,7 @@ export class TestHelper { name: rndStr() + '.jpg', directory: dir, metadata: m, - readyThumbnails: null, + readyThumbnails: [], readyIcon: false };