test(17-04): add failing tests for notification dispatcher

- TestSubscribeUnsubscribe: DB round-trip for add/remove subscriber
- TestNotifyNewFindings_NoSubscribers: zero messages with empty table
- TestNotifyNewFindings_ZeroFindings: no notification for 0 findings
- TestFormatNotification: message contains job name, count, duration
- TestFormatFindingNotification: masked key, never full key

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
salvacybersec
2026-04-06 17:32:58 +03:00
parent d671695f65
commit f7162aa34a

121
pkg/bot/subscribe_test.go Normal file
View File

@@ -0,0 +1,121 @@
package bot
import (
"testing"
"time"
"github.com/salvacybersec/keyhunter/pkg/engine"
"github.com/salvacybersec/keyhunter/pkg/scheduler"
"github.com/salvacybersec/keyhunter/pkg/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func openTestDB(t *testing.T) *storage.DB {
t.Helper()
db, err := storage.Open(":memory:")
require.NoError(t, err)
t.Cleanup(func() { _ = db.Close() })
return db
}
func TestSubscribeUnsubscribe(t *testing.T) {
db := openTestDB(t)
// Initially not subscribed.
ok, err := db.IsSubscribed(12345)
require.NoError(t, err)
assert.False(t, ok, "should not be subscribed initially")
// Subscribe.
err = db.AddSubscriber(12345, "testuser")
require.NoError(t, err)
ok, err = db.IsSubscribed(12345)
require.NoError(t, err)
assert.True(t, ok, "should be subscribed after AddSubscriber")
// Unsubscribe.
rows, err := db.RemoveSubscriber(12345)
require.NoError(t, err)
assert.Equal(t, int64(1), rows, "should have removed 1 row")
ok, err = db.IsSubscribed(12345)
require.NoError(t, err)
assert.False(t, ok, "should not be subscribed after RemoveSubscriber")
// Unsubscribe again returns 0 rows.
rows, err = db.RemoveSubscriber(12345)
require.NoError(t, err)
assert.Equal(t, int64(0), rows, "should have removed 0 rows when not subscribed")
}
func TestNotifyNewFindings_NoSubscribers(t *testing.T) {
db := openTestDB(t)
b := &Bot{cfg: Config{DB: db}}
sent, errs := b.NotifyNewFindings(scheduler.JobResult{
JobName: "nightly-scan",
FindingCount: 5,
Duration: 10 * time.Second,
})
assert.Equal(t, 0, sent, "should send 0 messages with no subscribers")
assert.Empty(t, errs, "should have no errors with no subscribers")
}
func TestNotifyNewFindings_ZeroFindings(t *testing.T) {
db := openTestDB(t)
_ = db.AddSubscriber(12345, "user1")
b := &Bot{cfg: Config{DB: db}}
sent, errs := b.NotifyNewFindings(scheduler.JobResult{
JobName: "nightly-scan",
FindingCount: 0,
Duration: 3 * time.Second,
})
assert.Equal(t, 0, sent, "should not notify for zero findings")
assert.Empty(t, errs, "should have no errors for zero findings")
}
func TestFormatNotification(t *testing.T) {
result := scheduler.JobResult{
JobName: "nightly-scan",
FindingCount: 7,
Duration: 2*time.Minute + 30*time.Second,
}
msg := formatNotification(result)
assert.Contains(t, msg, "nightly-scan", "message should contain job name")
assert.Contains(t, msg, "7", "message should contain finding count")
assert.Contains(t, msg, "2m30s", "message should contain duration")
assert.Contains(t, msg, "/stats", "message should reference /stats command")
}
func TestFormatNotification_Error(t *testing.T) {
result := scheduler.JobResult{
JobName: "daily-scan",
FindingCount: 0,
Duration: 5 * time.Second,
Error: assert.AnError,
}
msg := formatErrorNotification(result)
assert.Contains(t, msg, "daily-scan", "error message should contain job name")
assert.Contains(t, msg, "error", "error message should indicate error")
}
func TestFormatFindingNotification(t *testing.T) {
finding := engine.Finding{
ProviderName: "OpenAI",
KeyValue: "sk-proj-1234567890abcdef",
KeyMasked: "sk-proj-...cdef",
Confidence: "high",
Source: "/tmp/test.py",
LineNumber: 42,
}
msg := formatFindingNotification(finding)
assert.Contains(t, msg, "OpenAI", "should contain provider name")
assert.Contains(t, msg, "sk-proj-...cdef", "should contain masked key")
assert.NotContains(t, msg, "sk-proj-1234567890abcdef", "should NOT contain full key")
assert.Contains(t, msg, "/tmp/test.py", "should contain source path")
assert.Contains(t, msg, "42", "should contain line number")
assert.Contains(t, msg, "high", "should contain confidence")
}