Files
StarFleetCPTN b35174f857 feat: Introduce scheduler and notification enhancements
- Integrated new components for job scheduling, including a cron-based scheduler and improved job execution logic.
- Refactored job executor to utilize interfaces for better dependency management and testing.
- Enhanced notification system with a new Notifier interface, allowing for flexible notification service integration.
- Added comprehensive unit tests for job executor, scheduler, and notification components to ensure reliability and correctness.
- Updated metadata handling to support new database interfaces, improving testability and maintainability.
2025-03-29 21:23:52 -07:00

227 lines
7.1 KiB
Go

package scheduler
import (
"errors"
"fmt"
"strings"
"testing"
"github.com/starfleetcptn/gomft/internal/db"
"gorm.io/gorm" // Keep for gorm.ErrRecordNotFound
)
// --- Mock DB Implementation ---
// Ensure mockMetadataDB implements the MetadataDB interface
var _ MetadataDB = (*mockMetadataDB)(nil)
type mockMetadataDB struct {
GetFileMetadataByHashFunc func(hash string) (*db.FileMetadata, error)
GetFileMetadataByJobAndNameFunc func(jobID uint, fileName string) (*db.FileMetadata, error)
}
// Implement the MetadataDB interface methods
func (m *mockMetadataDB) GetFileMetadataByHash(hash string) (*db.FileMetadata, error) {
if m.GetFileMetadataByHashFunc != nil {
return m.GetFileMetadataByHashFunc(hash)
}
return nil, errors.New("mock GetFileMetadataByHashFunc not implemented")
}
func (m *mockMetadataDB) GetFileMetadataByJobAndName(jobID uint, fileName string) (*db.FileMetadata, error) {
if m.GetFileMetadataByJobAndNameFunc != nil {
return m.GetFileMetadataByJobAndNameFunc(jobID, fileName)
}
return nil, errors.New("mock GetFileMetadataByJobAndNameFunc not implemented")
}
// --- Tests ---
func TestHasFileBeenProcessed(t *testing.T) {
testJobID := uint(1)
testHash := "testhash123"
testMetadata := &db.FileMetadata{ID: 1, JobID: testJobID, FileHash: testHash, Status: "processed"}
dbErr := errors.New("database error")
logger, _ := newTestLogger(LogLevelDebug) // Use helper from logger_test
defer logger.Close()
tests := []struct {
name string
fileHash string
mockDBFunc func(hash string) (*db.FileMetadata, error)
wantProcessed bool
wantMetadata *db.FileMetadata
wantErr error
wantLogMessage string // Optional: check log output
}{
{
name: "Empty hash",
fileHash: "",
mockDBFunc: nil, // Not called
wantProcessed: false,
wantMetadata: nil,
wantErr: nil,
},
{
name: "Hash found",
fileHash: testHash,
mockDBFunc: func(hash string) (*db.FileMetadata, error) {
if hash == testHash {
return testMetadata, nil
}
return nil, gorm.ErrRecordNotFound
},
wantProcessed: true,
wantMetadata: testMetadata,
wantErr: nil,
wantLogMessage: "Found existing metadata by hash",
},
{
name: "Hash not found",
fileHash: testHash,
mockDBFunc: func(hash string) (*db.FileMetadata, error) {
return nil, gorm.ErrRecordNotFound
},
wantProcessed: false,
wantMetadata: nil,
wantErr: nil, // Not found is not an error for this function's return
},
{
name: "DB error",
fileHash: testHash,
mockDBFunc: func(hash string) (*db.FileMetadata, error) {
return nil, dbErr
},
wantProcessed: false,
wantMetadata: nil,
wantErr: dbErr, // The DB error should be returned
wantLogMessage: "Error checking metadata by hash",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockDB := &mockMetadataDB{ // Instantiate the mock implementing the interface
GetFileMetadataByHashFunc: tt.mockDBFunc,
}
// Recreate logger and buffer for each test run to isolate logs
logger, logBuf := newTestLogger(LogLevelDebug)
defer logger.Close()
// Pass the mockDB which now satisfies the MetadataDB interface
handler := NewMetadataHandler(mockDB, logger)
processed, metadata, err := handler.hasFileBeenProcessed(testJobID, tt.fileHash)
if processed != tt.wantProcessed {
t.Errorf("hasFileBeenProcessed() processed = %v, want %v", processed, tt.wantProcessed)
}
if metadata != tt.wantMetadata {
t.Errorf("hasFileBeenProcessed() metadata = %v, want %v", metadata, tt.wantMetadata)
}
if err != tt.wantErr {
t.Errorf("hasFileBeenProcessed() error = %v, want %v", err, tt.wantErr)
}
logOutput := logBuf.String()
if tt.wantLogMessage != "" && !strings.Contains(logOutput, tt.wantLogMessage) {
t.Errorf("Expected log message containing %q, but got:\n%s", tt.wantLogMessage, logOutput)
}
})
}
}
func TestCheckFileProcessingHistory(t *testing.T) {
testJobID := uint(1)
testFileName := "testfile.txt"
testMetadata := &db.FileMetadata{ID: 2, JobID: testJobID, FileName: testFileName, Status: "processed"}
dbErr := errors.New("database error")
notFoundErr := fmt.Errorf("no history found for file %s in job %d", testFileName, testJobID)
logger, _ := newTestLogger(LogLevelDebug) // Use helper from logger_test
defer logger.Close()
tests := []struct {
name string
jobID uint
fileName string
mockDBFunc func(jobID uint, fileName string) (*db.FileMetadata, error)
wantMetadata *db.FileMetadata
wantErr error // Check for specific error type/message
wantLogMessage string // Optional: check log output
}{
{
name: "History found",
jobID: testJobID,
fileName: testFileName,
mockDBFunc: func(jobID uint, fileName string) (*db.FileMetadata, error) {
if jobID == testJobID && fileName == testFileName {
return testMetadata, nil
}
return nil, gorm.ErrRecordNotFound
},
wantMetadata: testMetadata,
wantErr: nil,
wantLogMessage: "Found existing metadata by name",
},
{
name: "History not found",
jobID: testJobID,
fileName: testFileName,
mockDBFunc: func(jobID uint, fileName string) (*db.FileMetadata, error) {
return nil, gorm.ErrRecordNotFound
},
wantMetadata: nil,
wantErr: notFoundErr, // Expect the specific "no history found" error
},
{
name: "DB error",
jobID: testJobID,
fileName: testFileName,
mockDBFunc: func(jobID uint, fileName string) (*db.FileMetadata, error) {
return nil, dbErr
},
wantMetadata: nil,
wantErr: notFoundErr, // Even with DB error, it returns "no history found"
wantLogMessage: "Error checking metadata by name",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockDB := &mockMetadataDB{ // Instantiate the mock implementing the interface
GetFileMetadataByJobAndNameFunc: tt.mockDBFunc,
}
// Recreate logger and buffer for each test run
logger, logBuf := newTestLogger(LogLevelDebug)
defer logger.Close()
// Pass the mockDB which now satisfies the MetadataDB interface
handler := NewMetadataHandler(mockDB, logger)
metadata, err := handler.checkFileProcessingHistory(tt.jobID, tt.fileName)
if metadata != tt.wantMetadata {
t.Errorf("checkFileProcessingHistory() metadata = %v, want %v", metadata, tt.wantMetadata)
}
// Check error message specifically for "not found" cases
if tt.wantErr != nil {
if err == nil {
t.Errorf("checkFileProcessingHistory() error = nil, want error containing %q", tt.wantErr.Error())
} else if !strings.Contains(err.Error(), tt.wantErr.Error()) {
t.Errorf("checkFileProcessingHistory() error = %q, want error containing %q", err.Error(), tt.wantErr.Error())
}
} else if err != nil {
t.Errorf("checkFileProcessingHistory() error = %v, want nil", err)
}
logOutput := logBuf.String()
if tt.wantLogMessage != "" && !strings.Contains(logOutput, tt.wantLogMessage) {
t.Errorf("Expected log message containing %q, but got:\n%s", tt.wantLogMessage, logOutput)
}
})
}
}