diff --git a/app/botnew/spam.go b/app/botnew/spam.go index 4ee9b4b9..78b448e1 100644 --- a/app/botnew/spam.go +++ b/app/botnew/spam.go @@ -1,6 +1,7 @@ package bot import ( + "bytes" "context" "fmt" "io" @@ -147,42 +148,42 @@ func (s *SpamFilter) watch(ctx context.Context) error { return nil } -func (s *SpamFilter) reloadSamples() error { +func (s *SpamFilter) reloadSamples() (err error) { log.Printf("[DEBUG] reloading samples") - // open all files we need to reload - exclReader, err := os.Open(s.params.ExcludedTokensFile) - if err != nil { - return fmt.Errorf("failed to open excluded tokens file %s: %w", s.params.ExcludedTokensFile, err) - } - defer exclReader.Close() - spamReader, err := os.Open(s.params.SpamSamplesFile) - if err != nil { - return fmt.Errorf("failed to open spam samples file %s: %w", s.params.SpamSamplesFile, err) + var exclReader, spamReader, hamReader, stopWordsReader, spamDynamicReader, hamDynamicReader io.ReadCloser + + // open mandatory spam and ham samples files + if spamReader, err = os.Open(s.params.SpamSamplesFile); err != nil { + return fmt.Errorf("failed to open spam samples file %q: %w", s.params.SpamSamplesFile, err) } defer spamReader.Close() - hamReader, err := os.Open(s.params.HamSamplesFile) - if err != nil { - return fmt.Errorf("failed to open ham samples file %s: %w", s.params.HamSamplesFile, err) + if hamReader, err = os.Open(s.params.HamSamplesFile); err != nil { + return fmt.Errorf("failed to open ham samples file %q: %w", s.params.HamSamplesFile, err) } defer hamReader.Close() - stopWordsReader, err := os.Open(s.params.StopWordsFile) - if err != nil { - return fmt.Errorf("failed to open stop words file %s: %w", s.params.StopWordsFile, err) + // stop-words are optional + if stopWordsReader, err = os.Open(s.params.StopWordsFile); err != nil { + stopWordsReader = io.NopCloser(bytes.NewReader([]byte(""))) } defer stopWordsReader.Close() - spamDynamicReader, err := os.Open(s.params.SpamDynamicFile) - if err != nil { - return fmt.Errorf("failed to open spam dynamic file %s: %w", s.params.SpamDynamicFile, err) + // excluded tokens are optional + if exclReader, err = os.Open(s.params.ExcludedTokensFile); err != nil { + exclReader = io.NopCloser(bytes.NewReader([]byte(""))) + } + defer exclReader.Close() + + // dynamic samples are optional + if spamDynamicReader, err = os.Open(s.params.SpamDynamicFile); err != nil { + spamDynamicReader = io.NopCloser(bytes.NewReader([]byte(""))) } defer spamDynamicReader.Close() - hamDynamicReader, err := os.Open(s.params.HamDynamicFile) - if err != nil { - return fmt.Errorf("failed to open ham dynamic file %s: %w", s.params.HamDynamicFile, err) + if hamDynamicReader, err = os.Open(s.params.HamDynamicFile); err != nil { + hamDynamicReader = io.NopCloser(bytes.NewReader([]byte(""))) } defer hamDynamicReader.Close() diff --git a/app/botnew/spam_test.go b/app/botnew/spam_test.go index 8d2d9b78..e682ede9 100644 --- a/app/botnew/spam_test.go +++ b/app/botnew/spam_test.go @@ -1,9 +1,13 @@ package bot import ( + "errors" + "io" + "os" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/umputun/tg-spam/app/botnew/mocks" "github.com/umputun/tg-spam/lib" @@ -41,3 +45,115 @@ func TestSpamFilter_OnMessage(t *testing.T) { }) } + +func TestSpamFilter_reloadSamples(t *testing.T) { + var osOpen = os.Open // Mock for os.Open function + + // Mock os.Open + openOriginal := osOpen + defer func() { osOpen = openOriginal }() + osOpen = func(name string) (*os.File, error) { + if name == "fail" { + return nil, errors.New("open error") + } + if name == "notfound" { + return nil, os.ErrNotExist + } + return os.Open(os.DevNull) // Open a harmless file for other cases + } + + mockDirector := &mocks.DetectorMock{ + LoadSamplesFunc: func(exclReader io.Reader, spamReaders []io.Reader, hamReaders []io.Reader) (lib.LoadResult, error) { + return lib.LoadResult{}, nil + }, + LoadStopWordsFunc: func(readers ...io.Reader) (lib.LoadResult, error) { + return lib.LoadResult{}, nil + }, + } + + s := NewSpamFilter(mockDirector, SpamParams{ + SpamSamplesFile: "/dev/null", + HamSamplesFile: "/dev/null", + StopWordsFile: "optional", + ExcludedTokensFile: "optional", + SpamDynamicFile: "optional", + HamDynamicFile: "optional", + }) + + tests := []struct { + name string + modify func() + expectedErr error + }{ + { + name: "Successful execution", + modify: func() {}, + expectedErr: nil, + }, + { + name: "Spam samples file open failure", + modify: func() { + s.params.SpamSamplesFile = "fail" + }, + expectedErr: errors.New("failed to open spam samples file \"fail\": open fail: no such file or directory"), + }, + { + name: "Ham samples file open failure", + modify: func() { + s.params.HamSamplesFile = "fail" + }, + expectedErr: errors.New("failed to open ham samples file \"fail\": open fail: no such file or directory"), + }, + { + name: "Stop words file not found", + modify: func() { + s.params.StopWordsFile = "notfound" + }, + expectedErr: nil, + }, + { + name: "Excluded tokens file not found", + modify: func() { + s.params.ExcludedTokensFile = "notfound" + }, + expectedErr: nil, + }, + { + name: "Spam dynamic file not found", + modify: func() { + s.params.SpamDynamicFile = "notfound" + }, + expectedErr: nil, + }, + { + name: "Ham dynamic file not found", + modify: func() { + s.params.HamDynamicFile = "notfound" + }, + expectedErr: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Reset to default values before each test + s = NewSpamFilter(mockDirector, SpamParams{ + SpamSamplesFile: "/dev/null", + HamSamplesFile: "/dev/null", + StopWordsFile: "optional", + ExcludedTokensFile: "optional", + SpamDynamicFile: "optional", + HamDynamicFile: "optional", + }) + tc.modify() + err := s.reloadSamples() + + if tc.expectedErr != nil { + require.Error(t, err) + assert.Equal(t, tc.expectedErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +}