This commit introduces test cases for the API, archive, store, and filesystem functionalities, as well as a functional test for a full workflow. It ensures robust testing for snapshot operations, archiving, and blob management, significantly improving reliability.
		
			
				
	
	
		
			149 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			149 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package filesystem
 | 
						||
 | 
						||
import (
 | 
						||
	"context"
 | 
						||
	"fmt"
 | 
						||
	"io"
 | 
						||
	"os"
 | 
						||
	"path/filepath"
 | 
						||
 | 
						||
	"gitea.unprism.ru/KRBL/Agate/store"
 | 
						||
)
 | 
						||
 | 
						||
const blobExtension = ".zip"
 | 
						||
 | 
						||
// fileSystemStore реализует интерфейс store.BlobStore с использованием локальной файловой системы.
 | 
						||
type fileSystemStore struct {
 | 
						||
	baseDir   string // Директория для хранения блобов (архивов)
 | 
						||
	activeDir string // Директория для активных операций (создание и восстановление)
 | 
						||
}
 | 
						||
 | 
						||
// NewFileSystemStore создает новое хранилище блобов в указанной директории.
 | 
						||
func NewFileSystemStore(baseDir string) (store.BlobStore, error) {
 | 
						||
	// Убедимся, что базовая директория существует
 | 
						||
	if err := os.MkdirAll(baseDir, 0755); err != nil {
 | 
						||
		return nil, fmt.Errorf("failed to create base directory %s for filesystem blob store: %w", baseDir, err)
 | 
						||
	}
 | 
						||
 | 
						||
	// Создаем директорию для активных операций внутри базовой директории
 | 
						||
	activeDir := filepath.Join(baseDir, "active")
 | 
						||
	if err := os.MkdirAll(activeDir, 0755); err != nil {
 | 
						||
		return nil, fmt.Errorf("failed to create active directory %s for filesystem blob store: %w", activeDir, err)
 | 
						||
	}
 | 
						||
 | 
						||
	return &fileSystemStore{
 | 
						||
		baseDir:   baseDir,
 | 
						||
		activeDir: activeDir,
 | 
						||
	}, nil
 | 
						||
}
 | 
						||
 | 
						||
// getBlobPath формирует полный путь к файлу блоба.
 | 
						||
func (fs *fileSystemStore) getBlobPath(snapshotID string) string {
 | 
						||
	// Используем ID снапшота в качестве имени файла
 | 
						||
	return filepath.Join(fs.baseDir, snapshotID+blobExtension)
 | 
						||
}
 | 
						||
 | 
						||
// StoreBlob сохраняет данные из reader в файл в baseDir.
 | 
						||
func (fs *fileSystemStore) StoreBlob(ctx context.Context, snapshotID string, reader io.Reader) (string, error) {
 | 
						||
	blobPath := fs.getBlobPath(snapshotID)
 | 
						||
 | 
						||
	// Создаем или перезаписываем файл
 | 
						||
	file, err := os.Create(blobPath)
 | 
						||
	if err != nil {
 | 
						||
		return "", fmt.Errorf("failed to create blob file %s: %w", blobPath, err)
 | 
						||
	}
 | 
						||
	defer file.Close() // Гарантируем закрытие файла
 | 
						||
 | 
						||
	// Копируем данные из ридера в файл
 | 
						||
	_, err = io.Copy(file, reader)
 | 
						||
	if err != nil {
 | 
						||
		// Если произошла ошибка копирования, удаляем неполный файл
 | 
						||
		os.Remove(blobPath)
 | 
						||
		return "", fmt.Errorf("failed to write data to blob file %s: %w", blobPath, err)
 | 
						||
	}
 | 
						||
 | 
						||
	// Возвращаем путь к созданному файлу
 | 
						||
	return blobPath, nil
 | 
						||
}
 | 
						||
 | 
						||
// RetrieveBlob открывает файл блоба и возвращает его как io.ReadCloser.
 | 
						||
func (fs *fileSystemStore) RetrieveBlob(ctx context.Context, snapshotID string) (io.ReadCloser, error) {
 | 
						||
	blobPath := fs.getBlobPath(snapshotID)
 | 
						||
 | 
						||
	// Открываем файл для чтения
 | 
						||
	file, err := os.Open(blobPath)
 | 
						||
	if err != nil {
 | 
						||
		if os.IsNotExist(err) {
 | 
						||
			// Если файл не найден, возвращаем кастомную ошибку
 | 
						||
			return nil, store.ErrNotFound
 | 
						||
		}
 | 
						||
		return nil, fmt.Errorf("failed to open blob file %s: %w", blobPath, err)
 | 
						||
	}
 | 
						||
 | 
						||
	// Возвращаем открытый файл (*os.File реализует io.ReadCloser)
 | 
						||
	return file, nil
 | 
						||
}
 | 
						||
 | 
						||
// DeleteBlob удаляет файл блоба из файловой системы.
 | 
						||
func (fs *fileSystemStore) DeleteBlob(ctx context.Context, snapshotID string) error {
 | 
						||
	blobPath := fs.getBlobPath(snapshotID)
 | 
						||
 | 
						||
	// Удаляем файл
 | 
						||
	err := os.Remove(blobPath)
 | 
						||
	if err != nil {
 | 
						||
		if os.IsNotExist(err) {
 | 
						||
			// Если файл и так не существует, это не ошибка
 | 
						||
			return nil
 | 
						||
		}
 | 
						||
		// Если произошла другая ошибка при удалении
 | 
						||
		return fmt.Errorf("failed to delete blob file %s: %w", blobPath, err)
 | 
						||
	}
 | 
						||
 | 
						||
	return nil
 | 
						||
}
 | 
						||
 | 
						||
// GetBlobPath возвращает путь к файлу блоба, если он существует.
 | 
						||
func (fs *fileSystemStore) GetBlobPath(ctx context.Context, snapshotID string) (string, error) {
 | 
						||
	blobPath := fs.getBlobPath(snapshotID)
 | 
						||
 | 
						||
	// Проверяем существование файла
 | 
						||
	if _, err := os.Stat(blobPath); err != nil {
 | 
						||
		if os.IsNotExist(err) {
 | 
						||
			return "", store.ErrNotFound
 | 
						||
		}
 | 
						||
		return "", fmt.Errorf("failed to stat blob file %s: %w", blobPath, err)
 | 
						||
	}
 | 
						||
 | 
						||
	// Файл существует, возвращаем путь
 | 
						||
	return blobPath, nil
 | 
						||
}
 | 
						||
 | 
						||
// GetActiveDir возвращает путь к директории для активных операций.
 | 
						||
func (fs *fileSystemStore) GetBaseDir() string {
 | 
						||
	return fs.baseDir
 | 
						||
}
 | 
						||
 | 
						||
// GetActiveDir возвращает путь к директории для активных операций.
 | 
						||
func (fs *fileSystemStore) GetActiveDir() string {
 | 
						||
	return fs.activeDir
 | 
						||
}
 | 
						||
 | 
						||
// CleanActiveDir очищает директорию для активных операций.
 | 
						||
// Это полезно перед началом новых операций, чтобы избежать конфликтов.
 | 
						||
func (fs *fileSystemStore) CleanActiveDir(ctx context.Context) error {
 | 
						||
	// Удаляем все файлы в активной директории, но сохраняем саму директорию
 | 
						||
	entries, err := os.ReadDir(fs.activeDir)
 | 
						||
	if err != nil {
 | 
						||
		return fmt.Errorf("failed to read active directory: %w", err)
 | 
						||
	}
 | 
						||
 | 
						||
	for _, entry := range entries {
 | 
						||
		path := filepath.Join(fs.activeDir, entry.Name())
 | 
						||
		if err := os.RemoveAll(path); err != nil {
 | 
						||
			return fmt.Errorf("failed to remove %s from active directory: %w", path, err)
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	return nil
 | 
						||
}
 |