Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions e2e/src/generators/memory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { generateMemoriesFromTimeline, generateMemory } from './memory/model-objects';
export type { MemoryConfig, MemoryYearConfig } from './memory/model-objects';
84 changes: 84 additions & 0 deletions e2e/src/generators/memory/model-objects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { faker } from '@faker-js/faker';
import { MemoryType, type MemoryResponseDto, type OnThisDayDto } from '@immich/sdk';
import { DateTime } from 'luxon';
import { toAssetResponseDto } from 'src/generators/timeline/rest-response';
import type { MockTimelineAsset } from 'src/generators/timeline/timeline-config';
import { SeededRandom, selectRandomMultiple } from 'src/generators/timeline/utils';

export type MemoryConfig = {
id?: string;
ownerId: string;
year: number;
memoryAt: string;
isSaved?: boolean;
};

export type MemoryYearConfig = {
year: number;
assetCount: number;
};

export function generateMemory(config: MemoryConfig, assets: MockTimelineAsset[]): MemoryResponseDto {
const now = new Date().toISOString();
const memoryId = config.id ?? faker.string.uuid();

return {
id: memoryId,
assets: assets.map((asset) => toAssetResponseDto(asset)),
data: { year: config.year } as OnThisDayDto,
memoryAt: config.memoryAt,
createdAt: now,
updatedAt: now,
isSaved: config.isSaved ?? false,
ownerId: config.ownerId,
type: MemoryType.OnThisDay,
};
}

export function generateMemoriesFromTimeline(
timelineAssets: MockTimelineAsset[],
ownerId: string,
memoryConfigs: MemoryYearConfig[],
seed: number = 42,
): MemoryResponseDto[] {
const rng = new SeededRandom(seed);
const memories: MemoryResponseDto[] = [];
const usedAssetIds = new Set<string>();

for (const config of memoryConfigs) {
const yearAssets = timelineAssets.filter((asset) => {
const assetYear = DateTime.fromISO(asset.fileCreatedAt).year;
return assetYear === config.year && !usedAssetIds.has(asset.id);
});

if (yearAssets.length === 0) {
continue;
}

const countToSelect = Math.min(config.assetCount, yearAssets.length);
const selectedAssets = selectRandomMultiple(yearAssets, countToSelect, rng);

for (const asset of selectedAssets) {
usedAssetIds.add(asset.id);
}

selectedAssets.sort(
(a, b) => DateTime.fromISO(b.fileCreatedAt).diff(DateTime.fromISO(a.fileCreatedAt)).milliseconds,
);

const memoryAt = DateTime.now().set({ year: config.year }).toISO()!;

memories.push(
generateMemory(
{
ownerId,
year: config.year,
memoryAt,
},
selectedAssets,
),
);
}

return memories;
}
65 changes: 65 additions & 0 deletions e2e/src/mock-network/memory-network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { MemoryResponseDto } from '@immich/sdk';
import { BrowserContext } from '@playwright/test';

export type MemoryChanges = {
memoryDeletions: string[];
assetRemovals: Map<string, string[]>;
};

export const setupMemoryMockApiRoutes = async (
context: BrowserContext,
memories: MemoryResponseDto[],
changes: MemoryChanges,
) => {
await context.route('**/api/memories*', async (route, request) => {
const url = new URL(request.url());
const pathname = url.pathname;

if (pathname === '/api/memories' && request.method() === 'GET') {
const activeMemories = memories
.filter((memory) => !changes.memoryDeletions.includes(memory.id))
.map((memory) => {
const removedAssets = changes.assetRemovals.get(memory.id) ?? [];
return {
...memory,
assets: memory.assets.filter((asset) => !removedAssets.includes(asset.id)),
};
})
.filter((memory) => memory.assets.length > 0);

return route.fulfill({
status: 200,
contentType: 'application/json',
json: activeMemories,
});
}

const memoryMatch = pathname.match(/\/api\/memories\/([^/]+)$/);
if (memoryMatch && request.method() === 'GET') {
const memoryId = memoryMatch[1];
const memory = memories.find((m) => m.id === memoryId);

if (!memory || changes.memoryDeletions.includes(memoryId)) {
return route.fulfill({ status: 404 });
}

const removedAssets = changes.assetRemovals.get(memoryId) ?? [];
return route.fulfill({
status: 200,
contentType: 'application/json',
json: {
...memory,
assets: memory.assets.filter((asset) => !removedAssets.includes(asset.id)),
},
});
}

if (/\/api\/memories\/([^/]+)$/.test(pathname) && request.method() === 'DELETE') {
const memoryId = pathname.split('/').pop()!;
changes.memoryDeletions.push(memoryId);
return route.fulfill({ status: 204 });
}

await route.fallback();
});
};
Loading
Loading