mirror of
https://github.com/immich-app/immich.git
synced 2025-12-08 06:05:23 -05:00
* Test memory creation in advance Use year 2035 to make sure it's in the future of current time of a test run * Use target year instead of current year when fetching assets during memory creation This fixes an edge case of creating memories in advance when target year is different from current year. Example: job runs on 2025-12-31 (current year is 2025) and creates memories to be shown on 2026-01-01 (target year is 2026). If using _current_ year in calculation then range of years is capped at (2025 - 1 = 2024) thus excluding 2025-01-01 from created memories. With _target_ year it is (2026 - 1 = 2025), so 2025-01-01 will be included in memories. * Update sql queries
247 lines
8.8 KiB
TypeScript
247 lines
8.8 KiB
TypeScript
import { Kysely } from 'kysely';
|
|
import { DateTime } from 'luxon';
|
|
import { AssetFileType, MemoryType } from 'src/enum';
|
|
import { AccessRepository } from 'src/repositories/access.repository';
|
|
import { AssetRepository } from 'src/repositories/asset.repository';
|
|
import { DatabaseRepository } from 'src/repositories/database.repository';
|
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
|
import { MemoryRepository } from 'src/repositories/memory.repository';
|
|
import { PartnerRepository } from 'src/repositories/partner.repository';
|
|
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
|
import { UserRepository } from 'src/repositories/user.repository';
|
|
import { DB } from 'src/schema';
|
|
import { MemoryService } from 'src/services/memory.service';
|
|
import { 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(MemoryService, {
|
|
database: db || defaultDatabase,
|
|
real: [
|
|
AccessRepository,
|
|
AssetRepository,
|
|
DatabaseRepository,
|
|
MemoryRepository,
|
|
UserRepository,
|
|
SystemMetadataRepository,
|
|
UserRepository,
|
|
PartnerRepository,
|
|
],
|
|
mock: [LoggingRepository],
|
|
});
|
|
};
|
|
|
|
describe(MemoryService.name, () => {
|
|
beforeEach(async () => {
|
|
defaultDatabase = await getKyselyDB();
|
|
});
|
|
|
|
describe('create', () => {
|
|
it('should create a new memory', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const auth = factory.auth({ user });
|
|
const dto = {
|
|
type: MemoryType.OnThisDay,
|
|
data: { year: 2021 },
|
|
memoryAt: new Date(2021),
|
|
};
|
|
|
|
await expect(sut.create(auth, dto)).resolves.toEqual({
|
|
id: expect.any(String),
|
|
type: dto.type,
|
|
data: dto.data,
|
|
createdAt: expect.any(Date),
|
|
updatedAt: expect.any(Date),
|
|
isSaved: false,
|
|
memoryAt: dto.memoryAt,
|
|
ownerId: user.id,
|
|
assets: [],
|
|
});
|
|
});
|
|
|
|
it('should create a new memory (with assets)', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const { asset: asset1 } = await ctx.newAsset({ ownerId: user.id });
|
|
const { asset: asset2 } = await ctx.newAsset({ ownerId: user.id });
|
|
const auth = factory.auth({ user });
|
|
const dto = {
|
|
type: MemoryType.OnThisDay,
|
|
data: { year: 2021 },
|
|
memoryAt: new Date(2021),
|
|
assetIds: [asset1.id, asset2.id],
|
|
};
|
|
|
|
await expect(sut.create(auth, dto)).resolves.toEqual(
|
|
expect.objectContaining({
|
|
id: expect.any(String),
|
|
assets: [expect.objectContaining({ id: asset1.id }), expect.objectContaining({ id: asset2.id })],
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should create a new memory and ignore assets the user does not have access to', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user: user1 } = await ctx.newUser();
|
|
const { user: user2 } = await ctx.newUser();
|
|
const { asset: asset1 } = await ctx.newAsset({ ownerId: user1.id });
|
|
const { asset: asset2 } = await ctx.newAsset({ ownerId: user2.id });
|
|
const auth = factory.auth({ user: user1 });
|
|
const dto = {
|
|
type: MemoryType.OnThisDay,
|
|
data: { year: 2021 },
|
|
memoryAt: new Date(2021),
|
|
assetIds: [asset1.id, asset2.id],
|
|
};
|
|
|
|
await expect(sut.create(auth, dto)).resolves.toEqual(
|
|
expect.objectContaining({
|
|
id: expect.any(String),
|
|
assets: [expect.objectContaining({ id: asset1.id })],
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('onMemoryCreate', () => {
|
|
it('should work on an empty database', async () => {
|
|
const { sut } = setup();
|
|
await expect(sut.onMemoriesCreate()).resolves.not.toThrow();
|
|
});
|
|
|
|
it('should create a memory from an asset', async () => {
|
|
const { sut, ctx } = setup();
|
|
const assetRepo = ctx.get(AssetRepository);
|
|
const memoryRepo = ctx.get(MemoryRepository);
|
|
const now = DateTime.fromObject({ year: 2025, month: 2, day: 25 }, { zone: 'utc' }) as DateTime<true>;
|
|
const { user } = await ctx.newUser();
|
|
const { asset } = await ctx.newAsset({ ownerId: user.id, localDateTime: now.minus({ years: 1 }).toISO() });
|
|
await Promise.all([
|
|
ctx.newExif({ assetId: asset.id, make: 'Canon' }),
|
|
ctx.newJobStatus({ assetId: asset.id }),
|
|
assetRepo.upsertFiles([
|
|
{ assetId: asset.id, type: AssetFileType.Preview, path: '/path/to/preview.jpg' },
|
|
{ assetId: asset.id, type: AssetFileType.Thumbnail, path: '/path/to/thumbnail.jpg' },
|
|
]),
|
|
]);
|
|
|
|
vi.setSystemTime(now.toJSDate());
|
|
await sut.onMemoriesCreate();
|
|
|
|
const memories = await memoryRepo.search(user.id, {});
|
|
expect(memories.length).toBe(1);
|
|
expect(memories[0]).toEqual(
|
|
expect.objectContaining({
|
|
id: expect.any(String),
|
|
createdAt: expect.any(Date),
|
|
memoryAt: expect.any(Date),
|
|
updatedAt: expect.any(Date),
|
|
deletedAt: null,
|
|
ownerId: user.id,
|
|
assets: expect.arrayContaining([expect.objectContaining({ id: asset.id })]),
|
|
isSaved: false,
|
|
showAt: now.startOf('day').toJSDate(),
|
|
hideAt: now.endOf('day').toJSDate(),
|
|
seenAt: null,
|
|
type: 'on_this_day',
|
|
data: { year: 2024 },
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should create a memory from an asset - in advance', async () => {
|
|
const { sut, ctx } = setup();
|
|
const assetRepo = ctx.get(AssetRepository);
|
|
const memoryRepo = ctx.get(MemoryRepository);
|
|
const now = DateTime.fromObject({ year: 2035, month: 2, day: 26 }, { zone: 'utc' }) as DateTime<true>;
|
|
const { user } = await ctx.newUser();
|
|
const { asset } = await ctx.newAsset({ ownerId: user.id, localDateTime: now.minus({ years: 1 }).toISO() });
|
|
await Promise.all([
|
|
ctx.newExif({ assetId: asset.id, make: 'Canon' }),
|
|
ctx.newJobStatus({ assetId: asset.id }),
|
|
assetRepo.upsertFiles([
|
|
{ assetId: asset.id, type: AssetFileType.Preview, path: '/path/to/preview.jpg' },
|
|
{ assetId: asset.id, type: AssetFileType.Thumbnail, path: '/path/to/thumbnail.jpg' },
|
|
]),
|
|
]);
|
|
|
|
vi.setSystemTime(now.toJSDate());
|
|
await sut.onMemoriesCreate();
|
|
|
|
const memories = await memoryRepo.search(user.id, {});
|
|
expect(memories.length).toBe(1);
|
|
expect(memories[0]).toEqual(
|
|
expect.objectContaining({
|
|
id: expect.any(String),
|
|
createdAt: expect.any(Date),
|
|
memoryAt: expect.any(Date),
|
|
updatedAt: expect.any(Date),
|
|
deletedAt: null,
|
|
ownerId: user.id,
|
|
assets: expect.arrayContaining([expect.objectContaining({ id: asset.id })]),
|
|
isSaved: false,
|
|
showAt: now.startOf('day').toJSDate(),
|
|
hideAt: now.endOf('day').toJSDate(),
|
|
seenAt: null,
|
|
type: 'on_this_day',
|
|
data: { year: 2034 },
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should not generate a memory twice for the same day', async () => {
|
|
const { sut, ctx } = setup();
|
|
const assetRepo = ctx.get(AssetRepository);
|
|
const memoryRepo = ctx.get(MemoryRepository);
|
|
const now = DateTime.fromObject({ year: 2025, month: 2, day: 20 }, { zone: 'utc' }) as DateTime<true>;
|
|
const { user } = await ctx.newUser();
|
|
for (const dto of [
|
|
{
|
|
ownerId: user.id,
|
|
localDateTime: now.minus({ year: 1 }).plus({ days: 3 }).toISO(),
|
|
},
|
|
{
|
|
ownerId: user.id,
|
|
localDateTime: now.minus({ year: 1 }).plus({ days: 4 }).toISO(),
|
|
},
|
|
{
|
|
ownerId: user.id,
|
|
localDateTime: now.minus({ year: 1 }).plus({ days: 5 }).toISO(),
|
|
},
|
|
]) {
|
|
const { asset } = await ctx.newAsset(dto);
|
|
await Promise.all([
|
|
ctx.newExif({ assetId: asset.id, make: 'Canon' }),
|
|
ctx.newJobStatus({ assetId: asset.id }),
|
|
assetRepo.upsertFiles([
|
|
{ assetId: asset.id, type: AssetFileType.Preview, path: '/path/to/preview.jpg' },
|
|
{ assetId: asset.id, type: AssetFileType.Thumbnail, path: '/path/to/thumbnail.jpg' },
|
|
]),
|
|
]);
|
|
}
|
|
|
|
vi.setSystemTime(now.toJSDate());
|
|
await sut.onMemoriesCreate();
|
|
|
|
const memories = await memoryRepo.search(user.id, {});
|
|
expect(memories.length).toBe(1);
|
|
|
|
await sut.onMemoriesCreate();
|
|
|
|
const memoriesAfter = await memoryRepo.search(user.id, {});
|
|
expect(memoriesAfter.length).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('onMemoriesCleanup', () => {
|
|
it('should run without error', async () => {
|
|
const { sut } = setup();
|
|
await expect(sut.onMemoriesCleanup()).resolves.not.toThrow();
|
|
});
|
|
});
|
|
});
|