Add comprehensive test coverage for core functionalities

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.
This commit is contained in:
2025-05-10 20:13:29 +03:00
parent 65b1daa52c
commit 047e8d2df0
20 changed files with 3623 additions and 49 deletions

9
store/errors.go Normal file
View File

@ -0,0 +1,9 @@
package store
import "errors"
// Common errors that can be used by store implementations
var (
// ErrNotFound means that a requested resource was not found
ErrNotFound = errors.New("resource not found")
)

View File

@ -3,11 +3,11 @@ package filesystem
import (
"context"
"fmt"
"gitea.unprism.ru/KRBL/Agate"
"gitea.unprism.ru/KRBL/Agate/store"
"io"
"os"
"path/filepath"
"gitea.unprism.ru/KRBL/Agate/store"
)
const blobExtension = ".zip"
@ -75,7 +75,7 @@ func (fs *fileSystemStore) RetrieveBlob(ctx context.Context, snapshotID string)
if err != nil {
if os.IsNotExist(err) {
// Если файл не найден, возвращаем кастомную ошибку
return nil, agate.ErrNotFound
return nil, store.ErrNotFound
}
return nil, fmt.Errorf("failed to open blob file %s: %w", blobPath, err)
}
@ -109,7 +109,7 @@ func (fs *fileSystemStore) GetBlobPath(ctx context.Context, snapshotID string) (
// Проверяем существование файла
if _, err := os.Stat(blobPath); err != nil {
if os.IsNotExist(err) {
return "", agate.ErrNotFound
return "", store.ErrNotFound
}
return "", fmt.Errorf("failed to stat blob file %s: %w", blobPath, err)
}
@ -118,6 +118,11 @@ func (fs *fileSystemStore) GetBlobPath(ctx context.Context, snapshotID string) (
return blobPath, nil
}
// GetActiveDir возвращает путь к директории для активных операций.
func (fs *fileSystemStore) GetBaseDir() string {
return fs.baseDir
}
// GetActiveDir возвращает путь к директории для активных операций.
func (fs *fileSystemStore) GetActiveDir() string {
return fs.activeDir

View File

@ -0,0 +1,228 @@
package filesystem
import (
"bytes"
"context"
"io"
"os"
"path/filepath"
"testing"
)
func TestNewFileSystemStore(t *testing.T) {
// Create a temporary directory for tests
tempDir, err := os.MkdirTemp("", "agate-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir) // Clean up after test
// Create a new store
store, err := NewFileSystemStore(tempDir)
if err != nil {
t.Fatalf("Failed to create filesystem store: %v", err)
}
// Check that directories were created
if _, err := os.Stat(tempDir); os.IsNotExist(err) {
t.Fatalf("Base directory was not created")
}
// Check that the store's base directory matches the expected path
if store.GetBaseDir() != tempDir {
t.Fatalf("Store base directory does not match: got %s, want %s", store.GetBaseDir(), tempDir)
}
activeDir := filepath.Join(tempDir, "active")
if _, err := os.Stat(activeDir); os.IsNotExist(err) {
t.Fatalf("Active directory was not created")
}
// Check that the store's active directory matches the expected path
if store.GetActiveDir() != activeDir {
t.Fatalf("Store active directory does not match: got %s, want %s", store.GetActiveDir(), activeDir)
}
}
func TestStoreAndRetrieveBlob(t *testing.T) {
// Create a temporary directory for tests
tempDir, err := os.MkdirTemp("", "agate-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir) // Clean up after test
// Create a new store
store, err := NewFileSystemStore(tempDir)
if err != nil {
t.Fatalf("Failed to create filesystem store: %v", err)
}
// Create test data
testData := []byte("test data for blob")
reader := bytes.NewReader(testData)
ctx := context.Background()
// Store the blob
snapshotID := "test-snapshot-id"
path, err := store.StoreBlob(ctx, snapshotID, reader)
if err != nil {
t.Fatalf("Failed to store blob: %v", err)
}
// Check that the file was created
if _, err := os.Stat(path); os.IsNotExist(err) {
t.Fatalf("Blob file was not created")
}
// Retrieve the blob
blobReader, err := store.RetrieveBlob(ctx, snapshotID)
if err != nil {
t.Fatalf("Failed to retrieve blob: %v", err)
}
defer blobReader.Close()
// Read the data
retrievedData, err := io.ReadAll(blobReader)
if err != nil {
t.Fatalf("Failed to read blob data: %v", err)
}
// Check that the data matches
if !bytes.Equal(testData, retrievedData) {
t.Fatalf("Retrieved data does not match original data")
}
}
func TestDeleteBlob(t *testing.T) {
// Create a temporary directory for tests
tempDir, err := os.MkdirTemp("", "agate-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir) // Clean up after test
// Create a new store
store, err := NewFileSystemStore(tempDir)
if err != nil {
t.Fatalf("Failed to create filesystem store: %v", err)
}
// Create test data
testData := []byte("test data for blob")
reader := bytes.NewReader(testData)
ctx := context.Background()
// Store the blob
snapshotID := "test-snapshot-id"
path, err := store.StoreBlob(ctx, snapshotID, reader)
if err != nil {
t.Fatalf("Failed to store blob: %v", err)
}
// Delete the blob
err = store.DeleteBlob(ctx, snapshotID)
if err != nil {
t.Fatalf("Failed to delete blob: %v", err)
}
// Check that the file was deleted
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Fatalf("Blob file was not deleted")
}
// Deleting a non-existent blob should not return an error
err = store.DeleteBlob(ctx, "non-existent-id")
if err != nil {
t.Fatalf("DeleteBlob returned an error for non-existent blob: %v", err)
}
}
func TestGetBlobPath(t *testing.T) {
// Create a temporary directory for tests
tempDir, err := os.MkdirTemp("", "agate-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir) // Clean up after test
// Create a new store
store, err := NewFileSystemStore(tempDir)
if err != nil {
t.Fatalf("Failed to create filesystem store: %v", err)
}
// Create test data
testData := []byte("test data for blob")
reader := bytes.NewReader(testData)
ctx := context.Background()
// Store the blob
snapshotID := "test-snapshot-id"
expectedPath, err := store.StoreBlob(ctx, snapshotID, reader)
if err != nil {
t.Fatalf("Failed to store blob: %v", err)
}
// Get the blob path
path, err := store.GetBlobPath(ctx, snapshotID)
if err != nil {
t.Fatalf("Failed to get blob path: %v", err)
}
// Check that the path matches
if path != expectedPath {
t.Fatalf("GetBlobPath returned incorrect path: got %s, want %s", path, expectedPath)
}
// Getting path for non-existent blob should return ErrNotFound
_, err = store.GetBlobPath(ctx, "non-existent-id")
if err == nil {
t.Fatalf("GetBlobPath did not return an error for non-existent blob")
}
}
func TestCleanActiveDir(t *testing.T) {
// Create a temporary directory for tests
tempDir, err := os.MkdirTemp("", "agate-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir) // Clean up after test
// Create a new store
store, err := NewFileSystemStore(tempDir)
if err != nil {
t.Fatalf("Failed to create filesystem store: %v", err)
}
// Get the active directory
activeDir := store.GetActiveDir()
// Create some test files in the active directory
testFile1 := filepath.Join(activeDir, "test1.txt")
testFile2 := filepath.Join(activeDir, "test2.txt")
if err := os.WriteFile(testFile1, []byte("test1"), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
if err := os.WriteFile(testFile2, []byte("test2"), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Clean the active directory
ctx := context.Background()
err = store.CleanActiveDir(ctx)
if err != nil {
t.Fatalf("Failed to clean active directory: %v", err)
}
// Check that the files were deleted
entries, err := os.ReadDir(activeDir)
if err != nil {
t.Fatalf("Failed to read active directory: %v", err)
}
if len(entries) > 0 {
t.Fatalf("Active directory was not cleaned, %d files remain", len(entries))
}
}

View File

@ -6,7 +6,6 @@ import (
"encoding/json"
"errors"
"fmt"
"gitea.unprism.ru/KRBL/Agate"
"gitea.unprism.ru/KRBL/Agate/store"
_ "github.com/mattn/go-sqlite3"
"os"
@ -131,7 +130,7 @@ func (s *sqliteStore) GetSnapshotMetadata(ctx context.Context, snapshotID string
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// Если запись не найдена, возвращаем кастомную ошибку
return nil, agate.ErrNotFound
return nil, store.ErrNotFound
}
return nil, fmt.Errorf("failed to query snapshot %s: %w", snapshotID, err)
}

241
store/sqlite/sqlite_test.go Normal file
View File

@ -0,0 +1,241 @@
package sqlite
import (
"context"
"os"
"path/filepath"
"testing"
"time"
"gitea.unprism.ru/KRBL/Agate/store"
)
func TestNewSQLiteStore(t *testing.T) {
// Create a temporary directory for tests
tempDir, err := os.MkdirTemp("", "agate-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir) // Clean up after test
// Create a new store
dbPath := filepath.Join(tempDir, "test.db")
s, err := NewSQLiteStore(dbPath)
if err != nil {
t.Fatalf("Failed to create SQLite store: %v", err)
}
defer s.Close()
// Check that the database file was created
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
t.Fatalf("Database file was not created")
}
}
func TestSaveAndGetSnapshotMetadata(t *testing.T) {
// Create a temporary directory for tests
tempDir, err := os.MkdirTemp("", "agate-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir) // Clean up after test
// Create a new store
dbPath := filepath.Join(tempDir, "test.db")
s, err := NewSQLiteStore(dbPath)
if err != nil {
t.Fatalf("Failed to create SQLite store: %v", err)
}
defer s.Close()
// Create a test snapshot
now := time.Now().UTC().Truncate(time.Second) // SQLite doesn't store nanoseconds
testSnapshot := store.Snapshot{
ID: "test-snapshot-id",
Name: "Test Snapshot",
ParentID: "parent-snapshot-id",
CreationTime: now,
Files: []store.FileInfo{
{
Path: "/test/file1.txt",
Size: 100,
IsDir: false,
SHA256: "hash1",
},
{
Path: "/test/dir1",
Size: 0,
IsDir: true,
SHA256: "",
},
},
}
// Save the snapshot
ctx := context.Background()
err = s.SaveSnapshotMetadata(ctx, testSnapshot)
if err != nil {
t.Fatalf("Failed to save snapshot metadata: %v", err)
}
// Retrieve the snapshot
retrievedSnapshot, err := s.GetSnapshotMetadata(ctx, testSnapshot.ID)
if err != nil {
t.Fatalf("Failed to retrieve snapshot metadata: %v", err)
}
// Check that the retrieved snapshot matches the original
if retrievedSnapshot.ID != testSnapshot.ID {
t.Errorf("Retrieved snapshot ID does not match: got %s, want %s", retrievedSnapshot.ID, testSnapshot.ID)
}
if retrievedSnapshot.Name != testSnapshot.Name {
t.Errorf("Retrieved snapshot name does not match: got %s, want %s", retrievedSnapshot.Name, testSnapshot.Name)
}
if retrievedSnapshot.ParentID != testSnapshot.ParentID {
t.Errorf("Retrieved snapshot parent ID does not match: got %s, want %s", retrievedSnapshot.ParentID, testSnapshot.ParentID)
}
if !retrievedSnapshot.CreationTime.Equal(testSnapshot.CreationTime) {
t.Errorf("Retrieved snapshot creation time does not match: got %v, want %v", retrievedSnapshot.CreationTime, testSnapshot.CreationTime)
}
if len(retrievedSnapshot.Files) != len(testSnapshot.Files) {
t.Errorf("Retrieved snapshot has wrong number of files: got %d, want %d", len(retrievedSnapshot.Files), len(testSnapshot.Files))
}
}
func TestListSnapshotsMetadata(t *testing.T) {
// Create a temporary directory for tests
tempDir, err := os.MkdirTemp("", "agate-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir) // Clean up after test
// Create a new store
dbPath := filepath.Join(tempDir, "test.db")
s, err := NewSQLiteStore(dbPath)
if err != nil {
t.Fatalf("Failed to create SQLite store: %v", err)
}
defer s.Close()
// Create test snapshots
ctx := context.Background()
now := time.Now().UTC().Truncate(time.Second)
testSnapshots := []store.Snapshot{
{
ID: "snapshot-1",
Name: "Snapshot 1",
ParentID: "",
CreationTime: now.Add(-2 * time.Hour),
Files: []store.FileInfo{},
},
{
ID: "snapshot-2",
Name: "Snapshot 2",
ParentID: "snapshot-1",
CreationTime: now.Add(-1 * time.Hour),
Files: []store.FileInfo{},
},
{
ID: "snapshot-3",
Name: "Snapshot 3",
ParentID: "snapshot-2",
CreationTime: now,
Files: []store.FileInfo{},
},
}
// Save the snapshots
for _, snap := range testSnapshots {
err = s.SaveSnapshotMetadata(ctx, snap)
if err != nil {
t.Fatalf("Failed to save snapshot metadata: %v", err)
}
}
// List the snapshots
snapshots, err := s.ListSnapshotsMetadata(ctx)
if err != nil {
t.Fatalf("Failed to list snapshots: %v", err)
}
// Check that all snapshots are listed
if len(snapshots) != len(testSnapshots) {
t.Errorf("Wrong number of snapshots listed: got %d, want %d", len(snapshots), len(testSnapshots))
}
// Check that the snapshots have the correct information
for i, snap := range testSnapshots {
found := false
for _, listedSnap := range snapshots {
if listedSnap.ID == snap.ID {
found = true
if listedSnap.Name != snap.Name {
t.Errorf("Snapshot %d has wrong name: got %s, want %s", i, listedSnap.Name, snap.Name)
}
if listedSnap.ParentID != snap.ParentID {
t.Errorf("Snapshot %d has wrong parent ID: got %s, want %s", i, listedSnap.ParentID, snap.ParentID)
}
if !listedSnap.CreationTime.Equal(snap.CreationTime) {
t.Errorf("Snapshot %d has wrong creation time: got %v, want %v", i, listedSnap.CreationTime, snap.CreationTime)
}
break
}
}
if !found {
t.Errorf("Snapshot %d (%s) not found in listed snapshots", i, snap.ID)
}
}
}
func TestDeleteSnapshotMetadata(t *testing.T) {
// Create a temporary directory for tests
tempDir, err := os.MkdirTemp("", "agate-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir) // Clean up after test
// Create a new store
dbPath := filepath.Join(tempDir, "test.db")
s, err := NewSQLiteStore(dbPath)
if err != nil {
t.Fatalf("Failed to create SQLite store: %v", err)
}
defer s.Close()
// Create a test snapshot
ctx := context.Background()
testSnapshot := store.Snapshot{
ID: "test-snapshot-id",
Name: "Test Snapshot",
ParentID: "",
CreationTime: time.Now().UTC().Truncate(time.Second),
Files: []store.FileInfo{},
}
// Save the snapshot
err = s.SaveSnapshotMetadata(ctx, testSnapshot)
if err != nil {
t.Fatalf("Failed to save snapshot metadata: %v", err)
}
// Delete the snapshot
err = s.DeleteSnapshotMetadata(ctx, testSnapshot.ID)
if err != nil {
t.Fatalf("Failed to delete snapshot metadata: %v", err)
}
// Try to retrieve the deleted snapshot
_, err = s.GetSnapshotMetadata(ctx, testSnapshot.ID)
if err == nil {
t.Fatalf("Expected error when retrieving deleted snapshot, got nil")
}
// Deleting a non-existent snapshot should not return an error
err = s.DeleteSnapshotMetadata(ctx, "non-existent-id")
if err != nil {
t.Fatalf("DeleteSnapshotMetadata returned an error for non-existent snapshot: %v", err)
}
}

View File

@ -72,7 +72,10 @@ type BlobStore interface {
// Возвращает agate.ErrNotFound, если блоб не найден.
GetBlobPath(ctx context.Context, snapshotID string) (string, error)
// GetActiveDir возвращает путь к директории для активных операций (создание и восстановление).
// GetBaseDir возвращает путь к основной директории
GetBaseDir() string
// GetActiveDir возвращает путь к директории для активных операций.
GetActiveDir() string
// CleanActiveDir очищает директорию для активных операций.