Skip to content

Commit

Permalink
ensure proper permission after file rename for dynmaic spam
Browse files Browse the repository at this point in the history
fix test inconsistency

allow LoadSamplesCalls called >=1 times

due to background watcher
  • Loading branch information
umputun committed Mar 25, 2024
1 parent 5348639 commit 85881ab
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 13 deletions.
23 changes: 19 additions & 4 deletions app/bot/spam.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ func (s *SpamFilter) watch(ctx context.Context, delay time.Duration) error {
if !ok {
return
}
if event.Op == fsnotify.Remove {
// may happen when the file is renamed, ignore
continue
}
log.Printf("[DEBUG] file %q updated, op: %v", event.Name, event.Op)
if !reloadPending {
reloadPending = true
Expand Down Expand Up @@ -350,25 +354,36 @@ func (s *SpamFilter) removeDynamicSample(msg, fileName string) (int, error) {
for scanner.Scan() {
sample := scanner.Text()
if sample != msg {
if _, err := spamDynamicWriter.WriteString(sample + "\n"); err != nil {
if _, err = spamDynamicWriter.WriteString(sample + "\n"); err != nil {
return 0, fmt.Errorf("failed to write to temporary spam dynamic file: %w", err)
}
} else {
count++
}
}
if err := scanner.Err(); err != nil {
if err = scanner.Err(); err != nil {
return 0, fmt.Errorf("failed to read spam dynamic file: %w", err)
}

// ensure all writes are flushed to disk
if err := spamDynamicWriter.Sync(); err != nil {
if err = spamDynamicWriter.Sync(); err != nil {
return 0, fmt.Errorf("failed to flush writes to temporary file: %w", err)
}
if err := spamDynamicWriter.Close(); err != nil {
if err = spamDynamicWriter.Close(); err != nil {
return 0, fmt.Errorf("failed to close temporary spam dynamic file: %w", err)
}

// get the file info of the original file to retrieve its permissions
fileInfo, err := os.Stat(fileName)
if err != nil {
return 0, fmt.Errorf("failed to get file info of the original spam dynamic file: %w", err)
}

// set the permissions of the temporary file to match the original file
if err = os.Chmod(spamDynamicWriter.Name(), fileInfo.Mode()); err != nil {
return 0, fmt.Errorf("failed to set permissions of the temporary spam dynamic file: %w", err)
}

if count == 0 {
// not found, no need to replace the original file
if err := os.Remove(spamDynamicWriter.Name()); err != nil {
Expand Down
72 changes: 63 additions & 9 deletions app/bot/spam_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ func TestSpamFilter_DynamicSamples(t *testing.T) {
})
}

func TestSpamFilter_RemoveDynamiSample(t *testing.T) {
func TestSpamFilter_RemoveDynamicSample(t *testing.T) {
mockDirector := &mocks.DetectorMock{
LoadSamplesFunc: func(exclReader io.Reader, spamReaders []io.Reader, hamReaders []io.Reader) (tgspam.LoadResult, error) {
return tgspam.LoadResult{}, nil
Expand All @@ -474,26 +474,31 @@ func TestSpamFilter_RemoveDynamiSample(t *testing.T) {
prep := func() (res *SpamFilter, teardown func()) {
tmpDir, err := os.MkdirTemp("", "spamfilter_test")
require.NoError(t, err)

spamFile, err := os.CreateTemp("", "spam_dynamic")
t.Logf("tmpDir: %s", tmpDir)
spamFile, err := os.Create(filepath.Join(tmpDir, "spam_samples.txt"))
require.NoError(t, err)

hamFile, err := os.CreateTemp("", "ham_dynamic")
hamFile, err := os.Create(filepath.Join(tmpDir, "ham_samples.txt"))
require.NoError(t, err)

excludedTokensFile := filepath.Join(tmpDir, "excluded_tokens.txt")
spamSamplesFile := filepath.Join(tmpDir, "spam_samples.txt")
hamSamplesFile := filepath.Join(tmpDir, "ham_samples.txt")
stopWordsFile := filepath.Join(tmpDir, "stop_words.txt")

_, err = os.Create(excludedTokensFile)
fh, err := os.Create(excludedTokensFile)
require.NoError(t, err)
_, err = os.Create(spamSamplesFile)
fh.Close()

fh, err = os.Create(spamSamplesFile)
require.NoError(t, err)
_, err = os.Create(hamSamplesFile)
fh.Close()
fh, err = os.Create(hamSamplesFile)
require.NoError(t, err)
_, err = os.Create(stopWordsFile)
fh.Close()
fh, err = os.Create(stopWordsFile)
require.NoError(t, err)
fh.Close()

// Write sample data to the files
_, err = spamFile.WriteString("spam1\nspam2\nspam3\nspam3\n")
Expand All @@ -516,16 +521,17 @@ func TestSpamFilter_RemoveDynamiSample(t *testing.T) {
}

t.Run("remove from spam", func(t *testing.T) {
mockDirector.ResetCalls()
sf, teardown := prep()
defer teardown()

count, err := sf.RemoveDynamicSpamSample("spam1")
require.NoError(t, err)
assert.Equal(t, 1, count)
spam, ham, err := sf.DynamicSamples()
require.NoError(t, err)
assert.Equal(t, []string{"spam2", "spam3", "spam3"}, spam)
assert.Equal(t, []string{"ham1", "ham2"}, ham)
assert.True(t, len(mockDirector.LoadSamplesCalls()) >= 1, "LoadSamples should be called at least once")
})

t.Run("remove multi from spam", func(t *testing.T) {
Expand Down Expand Up @@ -566,5 +572,53 @@ func TestSpamFilter_RemoveDynamiSample(t *testing.T) {
assert.Equal(t, []string{"spam1", "spam2", "spam3", "spam3"}, spam)
assert.Equal(t, []string{"ham1", "ham2"}, ham)
})
}

func TestSpamFilter_RemoveDynamicSampleReal(t *testing.T) {
mockDirector := &mocks.DetectorMock{
LoadSamplesFunc: func(exclReader io.Reader, spamReaders []io.Reader, hamReaders []io.Reader) (tgspam.LoadResult, error) {
return tgspam.LoadResult{}, nil
},
LoadStopWordsFunc: func(readers ...io.Reader) (tgspam.LoadResult, error) {
return tgspam.LoadResult{}, nil
},
}

// make a temp file from testdata/spam.txt
tmpFile, err := os.CreateTemp("", "spam")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())
input, err := os.ReadFile("testdata/spam.txt")
require.NoError(t, err)
_, err = tmpFile.Write(input)
require.NoError(t, err)

tmpDir := filepath.Dir(tmpFile.Name())
spamFile, err := os.Create(filepath.Join(tmpDir, "spam_samples.txt"))
require.NoError(t, err)
hamFile, err := os.Create(filepath.Join(tmpDir, "ham_samples.txt"))
require.NoError(t, err)
excludedTokensFile := filepath.Join(tmpDir, "excluded_tokens.txt")
hamSamplesFile := filepath.Join(tmpDir, "ham_samples.txt")
stopWordsFile := filepath.Join(tmpDir, "stop_words.txt")

sf := NewSpamFilter(context.Background(), mockDirector, SpamConfig{
SpamDynamicFile: tmpFile.Name(),
HamDynamicFile: hamFile.Name(),
SpamSamplesFile: spamFile.Name(),
HamSamplesFile: hamSamplesFile,
StopWordsFile: stopWordsFile,
ExcludedTokensFile: excludedTokensFile,
})
spam, _, err := sf.DynamicSamples()
require.NoError(t, err)
assert.Contains(t, spam, "Здрαвствуйте, ищем ответственного человеκα для удαленной рαботы в новый проеκт!")

count, err := sf.RemoveDynamicSpamSample("Здрαвствуйте, ищем ответственного человеκα для удαленной рαботы в новый проеκт!")
require.NoError(t, err)
assert.Equal(t, 1, count, "should remove one sample")

spam, _, err = sf.DynamicSamples()
require.NoError(t, err)
assert.NotContains(t, spam, "Здрαвствуйте, ищем ответственного человеκα для удαленной рαботы в новый проеκт!")
}
Loading

0 comments on commit 85881ab

Please sign in to comment.