1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-07-13 01:20:23 +02:00

improving db scheme. Adding Mysql tests

This commit is contained in:
Patrik J. Braun
2019-01-27 14:36:42 -05:00
parent 8ed202b53d
commit d92003eeee
22 changed files with 342 additions and 150 deletions

View File

@ -1,9 +1,15 @@
dist: trusty dist: trusty
language: node_js language: node_js
node_js: node_js:
- '10' - '10'
- '11' - '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: addons:
chrome: stable chrome: stable
before_install: before_install:

View File

@ -222,7 +222,7 @@ export class GalleryMWs {
} }
public static async autocomplete(req: Request, res: Response, next: NextFunction) { 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(); return next();
} }
if (!(req.params.text)) { if (!(req.params.text)) {

View File

@ -17,16 +17,17 @@ import {DataStructureVersion} from '../../../common/DataStructureVersion';
import {FileEntity} from './enitites/FileEntity'; import {FileEntity} from './enitites/FileEntity';
import {FaceRegionEntry} from './enitites/FaceRegionEntry'; import {FaceRegionEntry} from './enitites/FaceRegionEntry';
import {PersonEntry} from './enitites/PersonEntry'; import {PersonEntry} from './enitites/PersonEntry';
import {Utils} from '../../../common/Utils';
export class SQLConnection { export class SQLConnection {
private static connection: Connection = null;
constructor() { constructor() {
} }
private static connection: Connection = null;
public static async getConnection(): Promise<Connection> { public static async getConnection(): Promise<Connection> {
if (this.connection == null) { if (this.connection == null) {
const options: any = this.getDriver(Config.Server.database); const options: any = this.getDriver(Config.Server.database);
@ -44,8 +45,10 @@ export class SQLConnection {
VersionEntity VersionEntity
]; ];
options.synchronize = false; options.synchronize = false;
//options.logging = 'all'; // options.logging = 'all';
this.connection = await createConnection(options);
this.connection = await this.createConnection(options);
await SQLConnection.schemeSync(this.connection); await SQLConnection.schemeSync(this.connection);
} }
return this.connection; return this.connection;
@ -72,7 +75,7 @@ export class SQLConnection {
]; ];
options.synchronize = false; options.synchronize = false;
// options.logging = "all"; // options.logging = "all";
const conn = await createConnection(options); const conn = await this.createConnection(options);
await SQLConnection.schemeSync(conn); await SQLConnection.schemeSync(conn);
await conn.close(); await conn.close();
return true; 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) { private static async schemeSync(connection: Connection) {
let version = null; let version = null;
try { try {
@ -145,16 +180,5 @@ export class SQLConnection {
return driver; return driver;
} }
public static async close() {
try {
if (this.connection != null) {
await this.connection.close();
this.connection = null;
}
} catch (err) {
console.error(err);
}
}
} }

View File

@ -9,6 +9,7 @@ import {VideoEntity} from './enitites/VideoEntity';
import {PersonEntry} from './enitites/PersonEntry'; import {PersonEntry} from './enitites/PersonEntry';
import {FaceRegionEntry} from './enitites/FaceRegionEntry'; import {FaceRegionEntry} from './enitites/FaceRegionEntry';
import {SelectQueryBuilder} from 'typeorm'; import {SelectQueryBuilder} from 'typeorm';
import {Config} from '../../../common/config/private/Config';
export class SearchManager implements ISearchManager { export class SearchManager implements ISearchManager {
@ -40,7 +41,7 @@ export class SearchManager implements ISearchManager {
.createQueryBuilder('photo') .createQueryBuilder('photo')
.select('DISTINCT(photo.metadata.keywords)') .select('DISTINCT(photo.metadata.keywords)')
.where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(5) .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.getRawMany()) .getRawMany())
.map(r => <Array<string>>(<string>r.metadataKeywords).split(',')) .map(r => <Array<string>>(<string>r.metadataKeywords).split(','))
.forEach(keywords => { .forEach(keywords => {
@ -52,7 +53,8 @@ export class SearchManager implements ISearchManager {
.createQueryBuilder('person') .createQueryBuilder('person')
.select('DISTINCT(person.name)') .select('DISTINCT(person.name)')
.where('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .where('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(5) .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.orderBy('person.name')
.getRawMany()) .getRawMany())
.map(r => r.name), SearchTypes.person)); .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.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('photo.metadata.positionData.city 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') .groupBy('photo.metadata.positionData.country, photo.metadata.positionData.state, photo.metadata.positionData.city')
.limit(5) .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.getRawMany()) .getRawMany())
.filter(pm => !!pm) .filter(pm => !!pm)
.map(pm => <Array<string>>[pm.city || '', pm.country || '', pm.state || '']) .map(pm => <Array<string>>[pm.city || '', pm.country || '', pm.state || ''])
@ -77,7 +79,7 @@ export class SearchManager implements ISearchManager {
.createQueryBuilder('media') .createQueryBuilder('media')
.select('DISTINCT(media.name)') .select('DISTINCT(media.name)')
.where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(5) .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.getRawMany()) .getRawMany())
.map(r => r.name), SearchTypes.photo)); .map(r => r.name), SearchTypes.photo));
@ -86,7 +88,7 @@ export class SearchManager implements ISearchManager {
.createQueryBuilder('media') .createQueryBuilder('media')
.select('DISTINCT(media.metadata.caption) as caption') .select('DISTINCT(media.metadata.caption) as caption')
.where('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .where('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(5) .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.getRawMany()) .getRawMany())
.map(r => r.caption), SearchTypes.photo)); .map(r => r.caption), SearchTypes.photo));
@ -95,7 +97,7 @@ export class SearchManager implements ISearchManager {
.createQueryBuilder('media') .createQueryBuilder('media')
.select('DISTINCT(media.name)') .select('DISTINCT(media.name)')
.where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(5) .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.getRawMany()) .getRawMany())
.map(r => r.name), SearchTypes.video)); .map(r => r.name), SearchTypes.video));
@ -103,7 +105,7 @@ export class SearchManager implements ISearchManager {
.createQueryBuilder('dir') .createQueryBuilder('dir')
.select('DISTINCT(dir.name)') .select('DISTINCT(dir.name)')
.where('dir.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .where('dir.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(5) .limit(Config.Client.Search.AutoComplete.maxItemsPerCategory)
.getRawMany()) .getRawMany())
.map(r => r.name), SearchTypes.directory)); .map(r => r.name), SearchTypes.directory));

View File

@ -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 {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
import {MediaEntity} from './MediaEntity'; import {MediaEntity} from './MediaEntity';
import {FileEntity} from './FileEntity'; import {FileEntity} from './FileEntity';
@ -8,7 +8,7 @@ import {FileEntity} from './FileEntity';
export class DirectoryEntity implements DirectoryDTO { export class DirectoryEntity implements DirectoryDTO {
@Index() @Index()
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn({unsigned: true})
id: number; id: number;
@Index() @Index()
@ -22,18 +22,28 @@ export class DirectoryEntity implements DirectoryDTO {
/** /**
* last time the directory was modified (from outside, eg.: a new media was added) * 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; public lastModified: number;
/** /**
* Last time the directory was fully scanned, not only for a few media to create a preview * 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; public lastScanned: number;
isPartial?: boolean; isPartial?: boolean;
@Column('smallint') @Column('smallint', {unsigned: true})
mediaCount: number; mediaCount: number;
@Index() @Index()

View File

@ -20,7 +20,7 @@ export class FaceRegionBoxEntry implements FaceRegionBox {
@Entity() @Entity()
export class FaceRegionEntry { export class FaceRegionEntry {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn({unsigned: true})
id: number; id: number;
@Column(type => FaceRegionBoxEntry) @Column(type => FaceRegionBoxEntry)

View File

@ -7,7 +7,7 @@ import {FileDTO} from '../../../../common/entities/FileDTO';
export class FileEntity implements FileDTO { export class FileEntity implements FileDTO {
@Index() @Index()
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn({unsigned: true})
id: number; id: number;
@Column('text') @Column('text')

View File

@ -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 {DirectoryEntity} from './DirectoryEntity';
import {MediaDimension, MediaDTO, MediaMetadata} from '../../../../common/entities/MediaDTO'; import {MediaDimension, MediaDTO, MediaMetadata} from '../../../../common/entities/MediaDTO';
import {OrientationTypes} from 'ts-exif-parser'; import {OrientationTypes} from 'ts-exif-parser';
@ -22,10 +22,15 @@ export class MediaMetadataEntity implements MediaMetadata {
@Column(type => MediaDimensionEntity) @Column(type => MediaDimensionEntity)
size: MediaDimensionEntity; size: MediaDimensionEntity;
@Column('bigint') @Column('bigint', {
unsigned: true, transformer: {
from: v => parseInt(v, 10),
to: v => v
}
})
creationDate: number; creationDate: number;
@Column('int') @Column('int', {unsigned: true})
fileSize: number; fileSize: number;
@Column('simple-array') @Column('simple-array')
@ -37,30 +42,30 @@ export class MediaMetadataEntity implements MediaMetadata {
@Column(type => PositionMetaDataEntity) @Column(type => PositionMetaDataEntity)
positionData: PositionMetaDataEntity; positionData: PositionMetaDataEntity;
@Column('tinyint', {default: OrientationTypes.TOP_LEFT}) @Column('tinyint', {unsigned: true, default: OrientationTypes.TOP_LEFT})
orientation: OrientationTypes; orientation: OrientationTypes;
@OneToMany(type => FaceRegionEntry, faceRegion => faceRegion.media) @OneToMany(type => FaceRegionEntry, faceRegion => faceRegion.media)
faces: FaceRegionEntry[]; faces: FaceRegionEntry[];
@Column('int') @Column('int', {unsigned: true})
bitRate: number; bitRate: number;
@Column('bigint') @Column('int', {unsigned: true})
duration: number; duration: number;
} }
// TODO: fix inheritance once its working in typeorm // TODO: fix inheritance once its working in typeorm
@Entity() @Entity()
@Unique(['name', 'directory']) @Unique(['name', 'directory'])
@TableInheritance({column: {type: 'varchar', name: 'type'}}) @TableInheritance({column: {type: 'varchar', name: 'type', length: 32}})
export abstract class MediaEntity implements MediaDTO { export abstract class MediaEntity implements MediaDTO {
@Index() @Index()
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn({unsigned: true})
id: number; id: number;
@Column('text') @Column()
name: string; name: string;
@Index() @Index()

View File

@ -6,7 +6,7 @@ import {FaceRegionEntry} from './FaceRegionEntry';
@Unique(['name']) @Unique(['name'])
export class PersonEntry { export class PersonEntry {
@Index() @Index()
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn({unsigned: true})
id: number; id: number;
@Column() @Column()

View File

@ -5,7 +5,7 @@ import {UserDTO} from '../../../../common/entities/UserDTO';
@Entity() @Entity()
export class SharingEntity implements SharingDTO { export class SharingEntity implements SharingDTO {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn({unsigned: true})
id: number; id: number;
@Column() @Column()
@ -17,10 +17,20 @@ export class SharingEntity implements SharingDTO {
@Column({type: 'text', nullable: true}) @Column({type: 'text', nullable: true})
password: string; password: string;
@Column() @Column('bigint', {
unsigned: true, transformer: {
from: v => parseInt(v, 10),
to: v => v
}
})
expires: number; expires: number;
@Column() @Column('bigint', {
unsigned: true, transformer: {
from: v => parseInt(v, 10),
to: v => v
}
})
timeStamp: number; timeStamp: number;
@Column() @Column()

View File

@ -1 +1 @@
export const DataStructureVersion = 8; export const DataStructureVersion = 9;

View File

@ -7,14 +7,19 @@ export module ClientConfig {
OpenStreetMap, Mapbox, Custom OpenStreetMap, Mapbox, Custom
} }
export interface AutoCompleteConfig {
enabled: boolean;
maxItemsPerCategory: number;
cacheTimeout: number;
}
export interface SearchConfig { export interface SearchConfig {
enabled: boolean; enabled: boolean;
instantSearchEnabled: boolean; instantSearchEnabled: boolean;
autocompleteEnabled: boolean;
InstantSearchTimeout: number; InstantSearchTimeout: number;
autocompleteCacheTimeout: number;
instantSearchCacheTimeout: number; instantSearchCacheTimeout: number;
searchCacheTimeout: number; searchCacheTimeout: number;
AutoComplete: AutoCompleteConfig;
} }
export interface SharingConfig { export interface SharingConfig {
@ -95,11 +100,14 @@ export class PublicConfigClass {
Search: { Search: {
enabled: true, enabled: true,
instantSearchEnabled: true, instantSearchEnabled: true,
autocompleteEnabled: true,
InstantSearchTimeout: 3000, InstantSearchTimeout: 3000,
autocompleteCacheTimeout: 1000 * 60 * 60,
searchCacheTimeout: 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: { Sharing: {
enabled: true, enabled: true,

View File

@ -77,7 +77,7 @@ export class GalleryCacheService {
const tmp = localStorage.getItem(key); const tmp = localStorage.getItem(key);
if (tmp != null) { if (tmp != null) {
const value: CacheItem<AutoCompleteItem[]> = JSON.parse(tmp); const value: CacheItem<AutoCompleteItem[]> = 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); localStorage.removeItem(key);
return null; return null;
} }

View File

@ -54,7 +54,7 @@ export class GallerySearchComponent implements OnDestroy {
const searchText = (<HTMLInputElement>event.target).value.trim(); const searchText = (<HTMLInputElement>event.target).value.trim();
if (Config.Client.Search.autocompleteEnabled && if (Config.Client.Search.AutoComplete.enabled &&
this.cache.lastAutocomplete !== searchText) { this.cache.lastAutocomplete !== searchText) {
this.cache.lastAutocomplete = searchText; this.cache.lastAutocomplete = searchText;
this.autocomplete(searchText).catch(console.error); this.autocomplete(searchText).catch(console.error);
@ -92,7 +92,7 @@ export class GallerySearchComponent implements OnDestroy {
} }
private async autocomplete(searchText: string) { private async autocomplete(searchText: string) {
if (!Config.Client.Search.autocompleteEnabled) { if (!Config.Client.Search.AutoComplete.enabled) {
return; return;
} }
if (searchText.trim() === '.') { if (searchText.trim() === '.') {

View File

@ -15,12 +15,15 @@ export class SettingsService {
Client: { Client: {
Search: { Search: {
enabled: true, enabled: true,
autocompleteEnabled: true, AutoComplete: {
enabled: true,
cacheTimeout: 1000 * 60 * 60,
maxItemsPerCategory: 5
},
instantSearchEnabled: true, instantSearchEnabled: true,
InstantSearchTimeout: 0, InstantSearchTimeout: 0,
searchCacheTimeout: 1000 * 60 * 60, searchCacheTimeout: 1000 * 60 * 60,
instantSearchCacheTimeout: 1000 * 60 * 60, instantSearchCacheTimeout: 1000 * 60 * 60,
autocompleteCacheTimeout: 1000 * 60 * 60
}, },
Thumbnail: { Thumbnail: {
concurrentThumbnailGenerations: null, concurrentThumbnailGenerations: null,

View File

@ -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();
}
}

View File

@ -16,7 +16,6 @@ import {
PositionMetaDataEntity PositionMetaDataEntity
} from '../../../../../backend/model/sql/enitites/PhotoEntity'; } from '../../../../../backend/model/sql/enitites/PhotoEntity';
import {MediaDimensionEntity} from '../../../../../backend/model/sql/enitites/MediaEntity'; import {MediaDimensionEntity} from '../../../../../backend/model/sql/enitites/MediaEntity';
import {DataStructureVersion} from '../../../../../common/DataStructureVersion';
import {VersionEntity} from '../../../../../backend/model/sql/enitites/VersionEntity'; import {VersionEntity} from '../../../../../backend/model/sql/enitites/VersionEntity';
describe('Typeorm integration', () => { describe('Typeorm integration', () => {

View File

@ -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<void> {
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 = <any>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));
});
});

View File

@ -1,8 +1,7 @@
import {expect} from 'chai'; import {expect} from 'chai';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path';
import {Config} from '../../../../../common/config/private/Config'; 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 {SQLConnection} from '../../../../../backend/model/sql/SQLConnection';
import {GalleryManager} from '../../../../../backend/model/sql/GalleryManager'; import {GalleryManager} from '../../../../../backend/model/sql/GalleryManager';
import {DirectoryDTO} from '../../../../../common/entities/DirectoryDTO'; import {DirectoryDTO} from '../../../../../common/entities/DirectoryDTO';
@ -15,6 +14,7 @@ import {FileDTO} from '../../../../../common/entities/FileDTO';
import {IndexingManager} from '../../../../../backend/model/sql/IndexingManager'; import {IndexingManager} from '../../../../../backend/model/sql/IndexingManager';
import {ObjectManagerRepository} from '../../../../../backend/model/ObjectManagerRepository'; import {ObjectManagerRepository} from '../../../../../backend/model/ObjectManagerRepository';
import {PersonManager} from '../../../../../backend/model/sql/PersonManager'; import {PersonManager} from '../../../../../backend/model/sql/PersonManager';
import {SQLTestHelper} from '../../../SQLTestHelper';
class GalleryManagerTest extends GalleryManager { 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 () => { 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) => { const removeIds = (dir: DirectoryDTO) => {
@ -245,7 +224,7 @@ describe('IndexingManager', () => {
expect(selected.media.length).to.deep.equal(subDir.media.length); expect(selected.media.length).to.deep.equal(subDir.media.length);
}) as any).timeout(40000); }) as any).timeout(40000);
describe('Test listDirectory', () => { SQLTestHelper.savedDescribe('Test listDirectory', () => {
const statSync = fs.statSync; const statSync = fs.statSync;
let dirTime = 0; let dirTime = 0;
const indexedTime = { const indexedTime = {

View File

@ -1,8 +1,4 @@
import {expect} from 'chai'; 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 {SQLConnection} from '../../../../../backend/model/sql/SQLConnection';
import {PhotoEntity} from '../../../../../backend/model/sql/enitites/PhotoEntity'; import {PhotoEntity} from '../../../../../backend/model/sql/enitites/PhotoEntity';
import {SearchManager} from '../../../../../backend/model/sql/SearchManager'; 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 {PersonEntry} from '../../../../../backend/model/sql/enitites/PersonEntry';
import {FaceRegionEntry} from '../../../../../backend/model/sql/enitites/FaceRegionEntry'; import {FaceRegionEntry} from '../../../../../backend/model/sql/enitites/FaceRegionEntry';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; 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;
describe('SearchManager', (sqlHelper: SQLTestHelper) => {
const tempDir = path.join(__dirname, '../../tmp');
const dbPath = path.join(tempDir, 'test.db');
const dir = TestHelper.getDirectoryEntry(); const dir = TestHelper.getDirectoryEntry();
const p = TestHelper.getPhotoEntry1(dir); const p = TestHelper.getPhotoEntry1(dir);
@ -31,15 +30,7 @@ describe('SearchManager', () => {
const v = TestHelper.getVideoEntry1(dir); const v = TestHelper.getVideoEntry1(dir);
const setUpSqlDB = async () => { const setUpSqlDB = async () => {
if (fs.existsSync(dbPath)) { await sqlHelper.initDB();
fs.unlinkSync(dbPath);
}
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir);
}
Config.Server.database.type = DatabaseType.sqlite;
Config.Server.database.sqlite.storage = dbPath;
const savePhoto = async (photo: PhotoDTO) => { const savePhoto = async (photo: PhotoDTO) => {
const savedPhoto = await pr.save(photo); const savedPhoto = await pr.save(photo);
@ -66,24 +57,15 @@ describe('SearchManager', () => {
await SQLConnection.close(); 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 () => { beforeEach(async () => {
await setUpSqlDB(); await setUpSqlDB();
}); });
afterEach(async () => {
await tearDownSqlDB();
});
after(async () => {
await sqlHelper.clearDB();
});
it('should get autocomplete', async () => { it('should get autocomplete', async () => {
const sm = new SearchManager(); const sm = new SearchManager();
@ -103,6 +85,8 @@ describe('SearchManager', () => {
new AutoCompleteItem('wars dir', SearchTypes.directory)]); new AutoCompleteItem('wars dir', SearchTypes.directory)]);
expect((await sm.autocomplete('arch'))).eql([new AutoCompleteItem('Research City', SearchTypes.position)]); 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([ expect((await sm.autocomplete('a')).sort(cmp)).eql([
new AutoCompleteItem('Boba Fett', SearchTypes.keyword), new AutoCompleteItem('Boba Fett', SearchTypes.keyword),
new AutoCompleteItem('Boba Fett', SearchTypes.person), new AutoCompleteItem('Boba Fett', SearchTypes.person),
@ -113,6 +97,7 @@ describe('SearchManager', () => {
new AutoCompleteItem('Han Solo', SearchTypes.person), new AutoCompleteItem('Han Solo', SearchTypes.person),
new AutoCompleteItem('death star', SearchTypes.keyword), new AutoCompleteItem('death star', SearchTypes.keyword),
new AutoCompleteItem('Padmé Amidala', SearchTypes.person), new AutoCompleteItem('Padmé Amidala', SearchTypes.person),
new AutoCompleteItem('Obivan Kenobi', SearchTypes.person),
new AutoCompleteItem('Padmé Amidala', SearchTypes.keyword), new AutoCompleteItem('Padmé Amidala', SearchTypes.keyword),
new AutoCompleteItem('Natalie Portman', SearchTypes.keyword), new AutoCompleteItem('Natalie Portman', SearchTypes.keyword),
new AutoCompleteItem('Han Solo\'s dice', SearchTypes.photo), new AutoCompleteItem('Han Solo\'s dice', SearchTypes.photo),
@ -121,10 +106,24 @@ describe('SearchManager', () => {
new AutoCompleteItem('wars dir', SearchTypes.directory), new AutoCompleteItem('wars dir', SearchTypes.directory),
new AutoCompleteItem('Research City', SearchTypes.position)].sort(cmp)); 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), 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)); 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)]); expect((await sm.autocomplete(v.name)).sort(cmp)).to.deep.equal([new AutoCompleteItem(v.name, SearchTypes.video)]);
}); });

View File

@ -1,32 +1,23 @@
import {expect} from 'chai'; 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 {SQLConnection} from '../../../../../backend/model/sql/SQLConnection';
import {SharingManager} from '../../../../../backend/model/sql/SharingManager'; import {SharingManager} from '../../../../../backend/model/sql/SharingManager';
import {SharingDTO} from '../../../../../common/entities/SharingDTO'; import {SharingDTO} from '../../../../../common/entities/SharingDTO';
import {UserEntity} from '../../../../../backend/model/sql/enitites/UserEntity'; import {UserEntity} from '../../../../../backend/model/sql/enitites/UserEntity';
import {UserDTO, UserRoles} from '../../../../../common/entities/UserDTO'; 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; let creator: UserDTO = null;
const setUpSqlDB = async () => { const setUpSqlDB = async () => {
if (fs.existsSync(dbPath)) { await sqlHelper.initDB();
fs.unlinkSync(dbPath);
}
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir);
}
Config.Server.database.type = DatabaseType.sqlite;
Config.Server.database.sqlite.storage = dbPath;
const conn = await SQLConnection.getConnection(); const conn = await SQLConnection.getConnection();
@ -41,22 +32,13 @@ describe('SharingManager', () => {
await SQLConnection.close(); 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 () => { beforeEach(async () => {
await setUpSqlDB(); await setUpSqlDB();
}); });
afterEach(async () => { after(async () => {
await teardownUpSqlDB(); await sqlHelper.clearDB();
}); });

View File

@ -258,7 +258,7 @@ export class TestHelper {
name: rndStr() + '.jpg', name: rndStr() + '.jpg',
directory: dir, directory: dir,
metadata: m, metadata: m,
readyThumbnails: null, readyThumbnails: [],
readyIcon: false readyIcon: false
}; };