mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-27 15:07:45 +00:00
fix: add asset upload medium test (#25144)
This commit is contained in:
parent
8136d7fd54
commit
191401f2f1
7 changed files with 120 additions and 4 deletions
BIN
mobile/openapi/lib/api/assets_api.dart
generated
BIN
mobile/openapi/lib/api/assets_api.dart
generated
Binary file not shown.
|
|
@ -15605,8 +15605,7 @@
|
||||||
"deviceAssetId",
|
"deviceAssetId",
|
||||||
"deviceId",
|
"deviceId",
|
||||||
"fileCreatedAt",
|
"fileCreatedAt",
|
||||||
"fileModifiedAt",
|
"fileModifiedAt"
|
||||||
"metadata"
|
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -484,7 +484,7 @@ export type AssetMediaCreateDto = {
|
||||||
filename?: string;
|
filename?: string;
|
||||||
isFavorite?: boolean;
|
isFavorite?: boolean;
|
||||||
livePhotoVideoId?: string;
|
livePhotoVideoId?: string;
|
||||||
metadata: AssetMetadataUpsertItemDto[];
|
metadata?: AssetMetadataUpsertItemDto[];
|
||||||
sidecarData?: Blob;
|
sidecarData?: Blob;
|
||||||
visibility?: AssetVisibility;
|
visibility?: AssetVisibility;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ export class AssetMediaCreateDto extends AssetMediaBase {
|
||||||
@Optional()
|
@Optional()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@IsArray()
|
@IsArray()
|
||||||
metadata!: AssetMetadataUpsertItemDto[];
|
metadata?: AssetMetadataUpsertItemDto[];
|
||||||
|
|
||||||
@ApiProperty({ type: 'string', format: 'binary', required: false })
|
@ApiProperty({ type: 'string', format: 'binary', required: false })
|
||||||
[UploadFieldName.SIDECAR_DATA]?: any;
|
[UploadFieldName.SIDECAR_DATA]?: any;
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,10 @@ export class AssetRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
upsertMetadata(id: string, items: Array<{ key: string; value: object }>) {
|
upsertMetadata(id: string, items: Array<{ key: string; value: object }>) {
|
||||||
|
if (items.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return this.db
|
return this.db
|
||||||
.insertInto('asset_metadata')
|
.insertInto('asset_metadata')
|
||||||
.values(items.map((item) => ({ assetId: id, ...item })))
|
.values(items.map((item) => ({ assetId: id, ...item })))
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ import { UserTable } from 'src/schema/tables/user.table';
|
||||||
import { BASE_SERVICE_DEPENDENCIES, BaseService } from 'src/services/base.service';
|
import { BASE_SERVICE_DEPENDENCIES, BaseService } from 'src/services/base.service';
|
||||||
import { MetadataService } from 'src/services/metadata.service';
|
import { MetadataService } from 'src/services/metadata.service';
|
||||||
import { SyncService } from 'src/services/sync.service';
|
import { SyncService } from 'src/services/sync.service';
|
||||||
|
import { UploadFile } from 'src/types';
|
||||||
import { mockEnvData } from 'test/repositories/config.repository.mock';
|
import { mockEnvData } from 'test/repositories/config.repository.mock';
|
||||||
import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock';
|
import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock';
|
||||||
import { factory, newDate, newEmbedding, newUuid } from 'test/small.factory';
|
import { factory, newDate, newEmbedding, newUuid } from 'test/small.factory';
|
||||||
|
|
@ -746,6 +747,17 @@ const loginResponse = (): LoginResponseDto => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const uploadFile = (file: Partial<UploadFile> = {}) => {
|
||||||
|
return {
|
||||||
|
uuid: newUuid(),
|
||||||
|
checksum: randomBytes(32),
|
||||||
|
originalPath: '/path/to/file.jpg',
|
||||||
|
originalName: 'file.jpg',
|
||||||
|
size: 123_456,
|
||||||
|
...file,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const mediumFactory = {
|
export const mediumFactory = {
|
||||||
assetInsert,
|
assetInsert,
|
||||||
assetFaceInsert,
|
assetFaceInsert,
|
||||||
|
|
@ -760,4 +772,5 @@ export const mediumFactory = {
|
||||||
loginDetails,
|
loginDetails,
|
||||||
loginResponse,
|
loginResponse,
|
||||||
tagInsert,
|
tagInsert,
|
||||||
|
uploadFile,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
100
server/test/medium/specs/services/asset-media.service.spec.ts
Normal file
100
server/test/medium/specs/services/asset-media.service.spec.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { Kysely } from 'kysely';
|
||||||
|
import { AssetMediaStatus } from 'src/dtos/asset-media-response.dto';
|
||||||
|
import { AccessRepository } from 'src/repositories/access.repository';
|
||||||
|
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||||
|
import { EventRepository } from 'src/repositories/event.repository';
|
||||||
|
import { JobRepository } from 'src/repositories/job.repository';
|
||||||
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||||
|
import { UserRepository } from 'src/repositories/user.repository';
|
||||||
|
import { DB } from 'src/schema';
|
||||||
|
import { AssetMediaService } from 'src/services/asset-media.service';
|
||||||
|
import { AssetService } from 'src/services/asset.service';
|
||||||
|
import { mediumFactory, newMediumService } from 'test/medium.factory';
|
||||||
|
import { factory } from 'test/small.factory';
|
||||||
|
import { getKyselyDB } from 'test/utils';
|
||||||
|
|
||||||
|
let defaultDatabase: Kysely<DB>;
|
||||||
|
|
||||||
|
const setup = (db?: Kysely<DB>) => {
|
||||||
|
return newMediumService(AssetMediaService, {
|
||||||
|
database: db || defaultDatabase,
|
||||||
|
real: [AccessRepository, AssetRepository, UserRepository],
|
||||||
|
mock: [EventRepository, LoggingRepository, JobRepository, StorageRepository],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
defaultDatabase = await getKyselyDB();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(AssetService.name, () => {
|
||||||
|
describe('uploadAsset', () => {
|
||||||
|
it('should work', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
|
||||||
|
ctx.getMock(StorageRepository).utimes.mockResolvedValue();
|
||||||
|
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||||
|
ctx.getMock(JobRepository).queue.mockResolvedValue();
|
||||||
|
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ctx.newExif({ assetId: asset.id, fileSizeInByte: 12_345 });
|
||||||
|
const auth = factory.auth({ user: { id: user.id } });
|
||||||
|
const file = mediumFactory.uploadFile();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
sut.uploadAsset(
|
||||||
|
auth,
|
||||||
|
{
|
||||||
|
deviceId: 'some-id',
|
||||||
|
deviceAssetId: 'some-id',
|
||||||
|
fileModifiedAt: new Date(),
|
||||||
|
fileCreatedAt: new Date(),
|
||||||
|
assetData: Buffer.from('some data'),
|
||||||
|
},
|
||||||
|
file,
|
||||||
|
),
|
||||||
|
).resolves.toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
status: AssetMediaStatus.CREATED,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ctx.getMock(EventRepository).emit).toHaveBeenCalledWith('AssetCreate', {
|
||||||
|
asset: expect.objectContaining({ deviceAssetId: 'some-id' }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with an empty metadata list', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
|
||||||
|
ctx.getMock(StorageRepository).utimes.mockResolvedValue();
|
||||||
|
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||||
|
ctx.getMock(JobRepository).queue.mockResolvedValue();
|
||||||
|
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ctx.newExif({ assetId: asset.id, fileSizeInByte: 12_345 });
|
||||||
|
const auth = factory.auth({ user: { id: user.id } });
|
||||||
|
const file = mediumFactory.uploadFile();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
sut.uploadAsset(
|
||||||
|
auth,
|
||||||
|
{
|
||||||
|
deviceId: 'some-id',
|
||||||
|
deviceAssetId: 'some-id',
|
||||||
|
fileModifiedAt: new Date(),
|
||||||
|
fileCreatedAt: new Date(),
|
||||||
|
assetData: Buffer.from('some data'),
|
||||||
|
metadata: [],
|
||||||
|
},
|
||||||
|
file,
|
||||||
|
),
|
||||||
|
).resolves.toEqual({
|
||||||
|
id: expect.any(String),
|
||||||
|
status: AssetMediaStatus.CREATED,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue