Files
picpeak/backend/__tests__
Marian 0d340f4e81 fix(archives): take the restored category from the manifest (#1240)
* fix(archives): take the restored category from the manifest

The archive writer already persists `category_name` per photo in
photos_manifest.json — that is why the manifest exists, and the comment
above it says so: "(and category linkage) can't be derived from the
extracted files alone". The restore route then read only
`original_filename` out of it and kept deriving the category from the
ZIP's first path segment.

Archives store photos exactly as they sit on disk, so an event whose
photos live in the gallery root produces a FLAT zip. `path.dirname()` is
'.' for every entry, no category is resolved, and every restored photo
lands with `category_id = null` — silently, behind a 200.

Seen on a real restore: 596 photos back, 0 with a category, while the
nine category rows sat untouched in the table.

Now the manifest is the source of truth and the first path segment is
the fallback, so foldered archives and legacy archives without a
manifest behave exactly as before. The find-or-create is pulled into
`resolveCategoryId` so both paths share it and each name is resolved
once per restore.

Tests: __tests__/integration/adminArchives.restoreCategories.test.js
builds real ZIPs (flat with manifest, flat with an existing category
row, foldered without manifest) and drives POST /:id/restore. Without
this change the two manifest cases fail and the foldered one passes —
the fallback is unchanged.

* fix(archives): let the manifest be authoritative when it says "no category"

Review follow-up on #1240, pushed with the author's agreement.

The manifest won for "category X" but not for "none": an entry with a null
category_name fell through to the directory fallback, so a photo the archive
recorded as uncategorized came back filed under a category anyway.

That matters because the directory is not a category. Archive entry names are
the storage key minus `events/active/{slug}`, and that layout is
`individual/{filename}` / `collages/{filename}` — categories have never been
directories there. Reading the first path segment on a real archive invents
categories literally named "individual" and "collages", so the fallback was
overriding an accurate record with a junk one.

The fallback is now confined to photos with NO manifest entry at all: archives
written before the manifest existed, where the directory is the only signal
left and inventing those names still beats losing every category.

Tests: the legacy case now uses `individual/`, the shape a real archive
actually has, instead of a category-shaped folder no archive produces — so it
documents what the fallback really does. Plus a new case pinning that a
manifest saying uncategorized leaves the photo uncategorized and creates no
category row. It fails without this change; the legacy fallback keeps passing.

---------

Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local>
2026-08-30 17:02:14 +02:00
..

Enhanced Backup System Test Suite

This directory contains comprehensive tests for the enhanced backup system with S3 support.

Test Structure

Unit Tests

  • services/backupService.enhanced.test.js - Unit tests for the enhanced backup service
    • Configuration management
    • S3 backup functionality
    • Manifest generation
    • Error handling and recovery
    • Backward compatibility (local and rsync)
    • Service lifecycle management

Integration Tests

  • integration/backup-s3.test.js - Integration tests for S3 backups
    • Real S3/MinIO connection tests
    • Full backup process with actual files
    • Incremental backup verification
    • Manifest storage and retrieval
    • Error recovery scenarios

Manual Integration Test Script

  • ../scripts/test-backup-integration.js - Comprehensive manual testing script
    • Can test against MinIO, AWS S3, or any S3-compatible service
    • Tests all backup types (S3, local, rsync)
    • Performance testing with large files
    • Detailed progress reporting

Running Tests

Prerequisites

  1. For Unit Tests: No special setup required, all dependencies are mocked.

  2. For Integration Tests: Requires a running S3-compatible service (MinIO recommended)

    # Start MinIO using Docker
    docker run -d \
      -p 9000:9000 \
      -p 9001:9001 \
      --name minio-test \
      -e MINIO_ROOT_USER=minioadmin \
      -e MINIO_ROOT_PASSWORD=minioadmin \
      minio/minio server /data --console-address ":9001"
    
  3. Environment Variables (for integration tests):

    # Optional - defaults work with local MinIO
    export TEST_S3_ENDPOINT=http://localhost:9000
    export TEST_S3_ACCESS_KEY=minioadmin
    export TEST_S3_SECRET_KEY=minioadmin
    
    # Skip S3 tests if no S3 service available
    export SKIP_S3_TESTS=true
    

Running Unit Tests

# Run all backup service tests
npm test -- __tests__/services/backupService.enhanced.test.js

# Run specific test suite
npm test -- __tests__/services/backupService.enhanced.test.js -t "S3 Backup Functionality"

# Run with coverage
npm test -- --coverage __tests__/services/backupService.enhanced.test.js

Running Integration Tests

# Ensure MinIO is running first!

# Run S3 integration tests
npm test -- __tests__/integration/backup-s3.test.js

# Run with verbose output
npm test -- __tests__/integration/backup-s3.test.js --verbose

# Skip S3 tests if needed
SKIP_S3_TESTS=true npm test -- __tests__/integration/backup-s3.test.js

Running Manual Integration Tests

# Test with local MinIO (default)
node scripts/test-backup-integration.js

# Test with AWS S3
node scripts/test-backup-integration.js \
  --endpoint https://s3.amazonaws.com \
  --access-key YOUR_ACCESS_KEY \
  --secret-key YOUR_SECRET_KEY \
  --bucket your-test-bucket

# Test local backup
node scripts/test-backup-integration.js --type local

# Test with cleanup after completion
node scripts/test-backup-integration.js --cleanup

# Verbose output
node scripts/test-backup-integration.js --verbose

Test Coverage

The test suite covers:

Configuration

  • Database configuration retrieval
  • JSON parsing and error handling
  • Configuration validation
  • Required field validation

S3 Functionality

  • S3 client initialization
  • Connection testing
  • File upload with progress tracking
  • Large file handling (multipart upload)
  • Metadata and custom headers
  • Error handling and retries

Backup Process

  • Full backup execution
  • Incremental backup (changed files only)
  • File checksum calculation and comparison
  • Database backup inclusion
  • Archive inclusion toggle
  • File size limits

Manifest Generation

  • Full manifest generation
  • Incremental manifest with parent reference
  • JSON and YAML format support
  • Manifest validation
  • S3 manifest storage and retrieval
  • Checksum verification

Error Handling

  • S3 connection failures
  • File read errors
  • Individual file failure recovery
  • Retry logic with exponential backoff
  • Email notifications on failure
  • Concurrent backup prevention

Backward Compatibility

  • Local directory backup
  • Rsync backup
  • Existing manifest format support

Service Management

  • Cron job scheduling
  • Service start/stop
  • Manual backup triggering
  • Backup history and status

Mock Setup

The unit tests use comprehensive mocking:

// Database mocking
jest.mock('../../src/database/db');

// S3 client mocking
jest.mock('../../src/services/storage/s3Storage');

// File system mocking
const mockFs = require('mock-fs');

// Cron job mocking
jest.mock('node-cron');

CI/CD Integration

To run tests in CI/CD pipeline:

# Example GitHub Actions
- name: Run Unit Tests
  run: npm test -- __tests__/services/backupService.enhanced.test.js

- name: Start MinIO
  run: |
    docker run -d \
      -p 9000:9000 \
      --name minio-test \
      -e MINIO_ROOT_USER=minioadmin \
      -e MINIO_ROOT_PASSWORD=minioadmin \
      minio/minio server /data

- name: Run Integration Tests
  run: npm test -- __tests__/integration/backup-s3.test.js

Debugging Tests

# Run tests in debug mode
node --inspect-brk ./node_modules/.bin/jest __tests__/services/backupService.enhanced.test.js

# Run single test with console output
npm test -- __tests__/services/backupService.enhanced.test.js -t "should perform S3 backup" --verbose

Performance Considerations

  • Integration tests create real files and S3 objects
  • Each test run creates a unique S3 bucket to avoid conflicts
  • Cleanup is automatic but can be disabled for debugging
  • Large file tests (10MB+) are included but can be slow

Adding New Tests

When adding new backup features:

  1. Add unit tests to backupService.enhanced.test.js
  2. Add integration tests to backup-s3.test.js if S3-specific
  3. Update manual test script for comprehensive testing
  4. Ensure mocks are properly configured
  5. Document any new environment requirements