0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-11 02:23:09 -05:00

fix(server): searchRandom response (#15580)

* fix searchRandom

* add e2e

* set outer limit
This commit is contained in:
Mert 2025-01-24 00:41:54 -05:00 committed by GitHub
parent 065d885ca0
commit ba105d9f19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 60 additions and 9 deletions

View file

@ -3,11 +3,11 @@ import {
AssetMediaStatus,
AssetResponseDto,
AssetTypeEnum,
LoginResponseDto,
SharedLinkType,
getAssetInfo,
getConfig,
getMyUser,
LoginResponseDto,
SharedLinkType,
updateConfig,
} from '@immich/sdk';
import { exiftool } from 'exiftool-vendored';
@ -19,7 +19,7 @@ import { Socket } from 'socket.io-client';
import { createUserDto, uuidDto } from 'src/fixtures';
import { makeRandomImage } from 'src/generators';
import { errorDto } from 'src/responses';
import { app, asBearerAuth, tempDir, testAssetDir, utils } from 'src/utils';
import { app, asBearerAuth, tempDir, TEN_TIMES, testAssetDir, utils } from 'src/utils';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
@ -41,8 +41,6 @@ const makeUploadDto = (options?: { omit: string }): Record<string, any> => {
return dto;
};
const TEN_TIMES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`;
const facesAssetFilepath = `${testAssetDir}/metadata/faces/portrait.jpg`;

View file

@ -1,10 +1,10 @@
import { AssetMediaResponseDto, LoginResponseDto, deleteAssets, updateAsset } from '@immich/sdk';
import { AssetMediaResponseDto, AssetResponseDto, deleteAssets, LoginResponseDto, updateAsset } from '@immich/sdk';
import { DateTime } from 'luxon';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { Socket } from 'socket.io-client';
import { errorDto } from 'src/responses';
import { app, asBearerAuth, testAssetDir, utils } from 'src/utils';
import { app, asBearerAuth, TEN_TIMES, testAssetDir, utils } from 'src/utils';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
const today = DateTime.now();
@ -462,6 +462,55 @@ describe('/search', () => {
});
});
describe('POST /search/random', () => {
beforeAll(async () => {
await Promise.all([
utils.createAsset(admin.accessToken),
utils.createAsset(admin.accessToken),
utils.createAsset(admin.accessToken),
utils.createAsset(admin.accessToken),
utils.createAsset(admin.accessToken),
utils.createAsset(admin.accessToken),
]);
await utils.waitForQueueFinish(admin.accessToken, 'thumbnailGeneration');
});
it('should require authentication', async () => {
const { status, body } = await request(app).post('/search/random').send({ size: 1 });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it.each(TEN_TIMES)('should return 1 random assets', async () => {
const { status, body } = await request(app)
.post('/search/random')
.send({ size: 1 })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
const assets: AssetResponseDto[] = body;
expect(assets.length).toBe(1);
expect(assets[0].ownerId).toBe(admin.userId);
});
it.each(TEN_TIMES)('should return 2 random assets', async () => {
const { status, body } = await request(app)
.post('/search/random')
.send({ size: 2 })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
const assets: AssetResponseDto[] = body;
expect(assets.length).toBe(2);
expect(assets[0].ownerId).toBe(admin.userId);
expect(assets[1].ownerId).toBe(admin.userId);
});
});
describe('GET /search/explore', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/search/explore');

View file

@ -76,6 +76,7 @@ export const immichCli = (args: string[]) =>
export const immichAdmin = (args: string[]) =>
executeCommand('docker', ['exec', '-i', 'immich-e2e-server', '/bin/bash', '-c', `immich-admin ${args.join(' ')}`]);
export const specialCharStrings = ["'", '"', ',', '{', '}', '*'];
export const TEN_TIMES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const executeCommand = (command: string, args: string[]) => {
let _resolve: (value: CommandResponse) => void;

View file

@ -60,6 +60,8 @@ union all
limit
$14
)
limit
$15
-- SearchRepository.searchSmart
select

View file

@ -69,12 +69,13 @@ export class SearchRepository implements ISearchRepository {
},
],
})
searchRandom(size: number, options: AssetSearchOptions): Promise<AssetEntity[]> {
async searchRandom(size: number, options: AssetSearchOptions): Promise<AssetEntity[]> {
const uuid = randomUUID();
const builder = searchAssetBuilder(this.db, options);
const lessThan = builder.where('assets.id', '<', uuid).orderBy('assets.id').limit(size);
const greaterThan = builder.where('assets.id', '>', uuid).orderBy('assets.id').limit(size);
return sql`${lessThan} union all ${greaterThan}`.execute(this.db) as any as Promise<AssetEntity[]>;
const { rows } = await sql`${lessThan} union all ${greaterThan} limit ${size}`.execute(this.db);
return rows as any as AssetEntity[];
}
@GenerateSql({