发布 0.1.0:通知渠道、告警规则、令牌版本与安全加固
This commit is contained in:
@@ -2,7 +2,9 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -28,7 +30,8 @@ func newLogEventEnv(t *testing.T) (*LogEventService, *gorm.DB, uint) {
|
||||
t.Fatalf("db handle: %v", err)
|
||||
}
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
if err := db.AutoMigrate(&model.Setting{}, &model.OciConfig{}, &model.LogEvent{}); err != nil {
|
||||
if err := db.AutoMigrate(&model.Setting{}, &model.OciConfig{}, &model.LogEvent{},
|
||||
&model.AlertRule{}, &model.AlertRuleHit{}); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
cfg := model.OciConfig{Alias: "测试租户"}
|
||||
@@ -98,9 +101,103 @@ func TestEnsureAndResolveSecret(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnsureSecretRejectsUnknownConfig(t *testing.T) {
|
||||
svc, _, _ := newLogEventEnv(t)
|
||||
if _, err := svc.EnsureSecret(context.Background(), 9999); err == nil {
|
||||
t.Fatal("ensure secret for unknown config succeeded, want error")
|
||||
svc, db, _ := newLogEventEnv(t)
|
||||
if _, err := svc.EnsureSecret(context.Background(), 9999); !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
t.Fatalf("ensure unknown config error = %v, want record not found", err)
|
||||
}
|
||||
var count int64
|
||||
db.Model(&model.Setting{}).Where("key = ?", secretKey(9999)).Count(&count)
|
||||
if count != 0 {
|
||||
t.Fatalf("unknown config secret rows = %d, want 0", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureSecretConcurrentDeleteNoOrphan(t *testing.T) {
|
||||
svc, db := newConcurrentSecretEnv(t)
|
||||
for i := 0; i < 8; i++ {
|
||||
cfg := model.OciConfig{Alias: fmt.Sprintf("race-%d", i)}
|
||||
if err := db.Create(&cfg).Error; err != nil {
|
||||
t.Fatalf("create config: %v", err)
|
||||
}
|
||||
ensureErr, deleteErr := runEnsureDeleteRace(svc, db, cfg.ID)
|
||||
if deleteErr != nil {
|
||||
t.Fatalf("delete config %d: %v", cfg.ID, deleteErr)
|
||||
}
|
||||
if ensureErr != nil && !errors.Is(ensureErr, gorm.ErrRecordNotFound) {
|
||||
t.Fatalf("ensure config %d: %v", cfg.ID, ensureErr)
|
||||
}
|
||||
assertNoSecretSetting(t, db, cfg.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func newConcurrentSecretEnv(t *testing.T) (*LogEventService, *gorm.DB) {
|
||||
t.Helper()
|
||||
dsn := filepath.Join(t.TempDir(), "secret.db") + "?_txlock=immediate&_pragma=busy_timeout%3d5000"
|
||||
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
||||
if err != nil {
|
||||
t.Fatalf("open sqlite: %v", err)
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
t.Fatalf("db handle: %v", err)
|
||||
}
|
||||
sqlDB.SetMaxOpenConns(4)
|
||||
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||
if err := db.AutoMigrate(&model.Setting{}, &model.OciConfig{}); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
return NewLogEventService(db), db
|
||||
}
|
||||
|
||||
func runEnsureDeleteRace(svc *LogEventService, db *gorm.DB, cfgID uint) (error, error) {
|
||||
start := make(chan struct{})
|
||||
ensureDone, deleteDone := make(chan error, 1), make(chan error, 1)
|
||||
go func() {
|
||||
<-start
|
||||
_, err := svc.EnsureSecret(context.Background(), cfgID)
|
||||
ensureDone <- err
|
||||
}()
|
||||
go func() {
|
||||
<-start
|
||||
deleteDone <- deleteConfigAndSecret(db, cfgID)
|
||||
}()
|
||||
close(start)
|
||||
return <-ensureDone, <-deleteDone
|
||||
}
|
||||
|
||||
func deleteConfigAndSecret(db *gorm.DB, cfgID uint) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := lockOciConfig(tx, cfgID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Delete(&model.Setting{}, "key = ?", secretKey(cfgID)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Delete(&model.OciConfig{}, cfgID).Error
|
||||
})
|
||||
}
|
||||
|
||||
func assertNoSecretSetting(t *testing.T, db *gorm.DB, cfgID uint) {
|
||||
t.Helper()
|
||||
var count int64
|
||||
db.Model(&model.Setting{}).Where("key = ?", secretKey(cfgID)).Count(&count)
|
||||
if count != 0 {
|
||||
t.Fatalf("orphan secret rows for config %d = %d", cfgID, count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveSecretRejectsOrphanSetting(t *testing.T) {
|
||||
svc, db, _ := newLogEventEnv(t)
|
||||
orphan := model.Setting{Key: secretKey(9999), Value: "orphan-secret"}
|
||||
if err := db.Create(&orphan).Error; err != nil {
|
||||
t.Fatalf("create orphan secret: %v", err)
|
||||
}
|
||||
if id, ok := svc.ResolveSecret(context.Background(), orphan.Value); ok || id != 0 {
|
||||
t.Fatalf("resolve orphan secret = (%d,%v), want (0,false)", id, ok)
|
||||
}
|
||||
info, ok, err := svc.SecretInfo(context.Background(), 9999)
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) || ok || info.Secret != "" {
|
||||
t.Fatalf("orphan secret info = (%q,%v,%v), want empty,false,record not found", info.Secret, ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +224,19 @@ func TestIngestIdempotent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngestRejectsUnknownConfig(t *testing.T) {
|
||||
svc, db, _ := newLogEventEnv(t)
|
||||
err := svc.Ingest(context.Background(), 9999, "orphan", []byte(`{}`), false)
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
t.Fatalf("ingest unknown config error = %v, want record not found", err)
|
||||
}
|
||||
var count int64
|
||||
db.Model(&model.LogEvent{}).Where("message_id = ?", "orphan").Count(&count)
|
||||
if count != 0 {
|
||||
t.Fatalf("orphan event rows = %d, want 0", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLogEvent(t *testing.T) {
|
||||
ts := "2026-07-07T08:00:00Z"
|
||||
tests := []struct {
|
||||
@@ -209,6 +319,40 @@ func TestParseOnceMarksProcessed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateParsedEventDoesNotRecreateDeleted(t *testing.T) {
|
||||
svc, db, cfgID := newLogEventEnv(t)
|
||||
event := model.LogEvent{OciConfigID: cfgID, MessageID: "deleted", Payload: `{}`}
|
||||
if err := db.Create(&event).Error; err != nil {
|
||||
t.Fatalf("create event: %v", err)
|
||||
}
|
||||
if err := db.Delete(&model.LogEvent{}, event.ID).Error; err != nil {
|
||||
t.Fatalf("delete event: %v", err)
|
||||
}
|
||||
if svc.updateParsedEvent(context.Background(), &event, parsedEvent{EventType: "x"}) {
|
||||
t.Fatal("deleted event update reported success")
|
||||
}
|
||||
var count int64
|
||||
db.Model(&model.LogEvent{}).Where("id = ?", event.ID).Count(&count)
|
||||
if count != 0 {
|
||||
t.Fatalf("deleted event was recreated, rows = %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateParsedEventOnlyOnce(t *testing.T) {
|
||||
svc, db, cfgID := newLogEventEnv(t)
|
||||
event := model.LogEvent{OciConfigID: cfgID, MessageID: "once", Payload: `{}`}
|
||||
if err := db.Create(&event).Error; err != nil {
|
||||
t.Fatalf("create event: %v", err)
|
||||
}
|
||||
parsed := parsedEvent{EventType: "x"}
|
||||
if !svc.updateParsedEvent(context.Background(), &event, parsed) {
|
||||
t.Fatal("first conditional update should succeed")
|
||||
}
|
||||
if svc.updateParsedEvent(context.Background(), &event, parsed) {
|
||||
t.Fatal("processed event should not update twice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogEventCleanup(t *testing.T) {
|
||||
svc, db, cfgID := newLogEventEnv(t)
|
||||
ctx := context.Background()
|
||||
|
||||
Reference in New Issue
Block a user