mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:39:37 -05:00 
			
		
		
		
	fix(server): exclude archived assets from orphaned files (#7334)
* fix(server): exclude archived assets from orphaned files * add e2e test
This commit is contained in:
		
							parent
							
								
									b3131dfe14
								
							
						
					
					
						commit
						07ef008b40
					
				@ -4,6 +4,7 @@ name: immich-e2e
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
x-server-build: &server-common
 | 
					x-server-build: &server-common
 | 
				
			||||||
  image: immich-server:latest
 | 
					  image: immich-server:latest
 | 
				
			||||||
 | 
					  container_name: immich-e2e-server
 | 
				
			||||||
  build:
 | 
					  build:
 | 
				
			||||||
    context: ../
 | 
					    context: ../
 | 
				
			||||||
    dockerfile: server/Dockerfile
 | 
					    dockerfile: server/Dockerfile
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										51
									
								
								e2e/src/api/specs/audit.e2e-spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								e2e/src/api/specs/audit.e2e-spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  deleteAssets,
 | 
				
			||||||
 | 
					  getAuditFiles,
 | 
				
			||||||
 | 
					  updateAsset,
 | 
				
			||||||
 | 
					  type LoginResponseDto,
 | 
				
			||||||
 | 
					} from '@immich/sdk';
 | 
				
			||||||
 | 
					import { apiUtils, asBearerAuth, dbUtils, fileUtils } from 'src/utils';
 | 
				
			||||||
 | 
					import { beforeAll, describe, expect, it } from 'vitest';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('/audit', () => {
 | 
				
			||||||
 | 
					  let admin: LoginResponseDto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    apiUtils.setup();
 | 
				
			||||||
 | 
					    await dbUtils.reset();
 | 
				
			||||||
 | 
					    await fileUtils.reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    admin = await apiUtils.adminSetup();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('GET :/file-report', () => {
 | 
				
			||||||
 | 
					    it('excludes assets without issues from report', async () => {
 | 
				
			||||||
 | 
					      const [trashedAsset, archivedAsset, _] = await Promise.all([
 | 
				
			||||||
 | 
					        apiUtils.createAsset(admin.accessToken),
 | 
				
			||||||
 | 
					        apiUtils.createAsset(admin.accessToken),
 | 
				
			||||||
 | 
					        apiUtils.createAsset(admin.accessToken),
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await Promise.all([
 | 
				
			||||||
 | 
					        deleteAssets(
 | 
				
			||||||
 | 
					          { assetBulkDeleteDto: { ids: [trashedAsset.id] } },
 | 
				
			||||||
 | 
					          { headers: asBearerAuth(admin.accessToken) }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        updateAsset(
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            id: archivedAsset.id,
 | 
				
			||||||
 | 
					            updateAssetDto: { isArchived: true },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          { headers: asBearerAuth(admin.accessToken) }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const body = await getAuditFiles({
 | 
				
			||||||
 | 
					        headers: asBearerAuth(admin.accessToken),
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(body.orphans).toHaveLength(0);
 | 
				
			||||||
 | 
					      expect(body.extras).toHaveLength(0);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -17,14 +17,17 @@ import {
 | 
				
			|||||||
  updatePerson,
 | 
					  updatePerson,
 | 
				
			||||||
} from '@immich/sdk';
 | 
					} from '@immich/sdk';
 | 
				
			||||||
import { BrowserContext } from '@playwright/test';
 | 
					import { BrowserContext } from '@playwright/test';
 | 
				
			||||||
import { spawn } from 'child_process';
 | 
					import { exec, spawn } from 'child_process';
 | 
				
			||||||
import { randomBytes } from 'node:crypto';
 | 
					import { randomBytes } from 'node:crypto';
 | 
				
			||||||
import { access } from 'node:fs/promises';
 | 
					import { access } from 'node:fs/promises';
 | 
				
			||||||
import path from 'node:path';
 | 
					import path from 'node:path';
 | 
				
			||||||
 | 
					import { promisify } from 'node:util';
 | 
				
			||||||
import pg from 'pg';
 | 
					import pg from 'pg';
 | 
				
			||||||
import { loginDto, signupDto } from 'src/fixtures';
 | 
					import { loginDto, signupDto } from 'src/fixtures';
 | 
				
			||||||
import request from 'supertest';
 | 
					import request from 'supertest';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const execPromise = promisify(exec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const app = 'http://127.0.0.1:2283/api';
 | 
					export const app = 'http://127.0.0.1:2283/api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const directoryExists = (directory: string) =>
 | 
					const directoryExists = (directory: string) =>
 | 
				
			||||||
@ -35,6 +38,9 @@ const directoryExists = (directory: string) =>
 | 
				
			|||||||
// TODO move test assets into e2e/assets
 | 
					// TODO move test assets into e2e/assets
 | 
				
			||||||
export const testAssetDir = path.resolve(`./../server/test/assets/`);
 | 
					export const testAssetDir = path.resolve(`./../server/test/assets/`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const serverContainerName = 'immich-e2e-server';
 | 
				
			||||||
 | 
					const uploadMediaDir = '/usr/src/app/upload/upload';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (!(await directoryExists(`${testAssetDir}/albums`))) {
 | 
					if (!(await directoryExists(`${testAssetDir}/albums`))) {
 | 
				
			||||||
  throw new Error(
 | 
					  throw new Error(
 | 
				
			||||||
    `Test assets not found. Please checkout https://github.com/immich-app/test-assets into ${testAssetDir} before testing`
 | 
					    `Test assets not found. Please checkout https://github.com/immich-app/test-assets into ${testAssetDir} before testing`
 | 
				
			||||||
@ -50,6 +56,14 @@ export const asKeyAuth = (key: string) => ({ 'x-api-key': key });
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
let client: pg.Client | null = null;
 | 
					let client: pg.Client | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const fileUtils = {
 | 
				
			||||||
 | 
					  reset: async () => {
 | 
				
			||||||
 | 
					    await execPromise(
 | 
				
			||||||
 | 
					      `docker exec -i "${serverContainerName}" rm -R "${uploadMediaDir}"`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const dbUtils = {
 | 
					export const dbUtils = {
 | 
				
			||||||
  createFace: async ({
 | 
					  createFace: async ({
 | 
				
			||||||
    assetId,
 | 
					    assetId,
 | 
				
			||||||
 | 
				
			|||||||
@ -167,7 +167,7 @@ export class AuditService {
 | 
				
			|||||||
      `Found ${libraryFiles.size} original files, ${thumbFiles.size} thumbnails, ${videoFiles.size} encoded videos, ${profileFiles.size} profile files`,
 | 
					      `Found ${libraryFiles.size} original files, ${thumbFiles.size} thumbnails, ${videoFiles.size} encoded videos, ${profileFiles.size} profile files`,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const pagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (options) =>
 | 
					    const pagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (options) =>
 | 
				
			||||||
      this.assetRepository.getAll(options, { withDeleted: true }),
 | 
					      this.assetRepository.getAll(options, { withDeleted: true, withArchived: true }),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let assetCount = 0;
 | 
					    let assetCount = 0;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user