From 8af88968b12004022219af9b04c2d4fdfe9099fe Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Thu, 1 Oct 2026 21:00:44 +0800 Subject: [PATCH 1/2] =?UTF-8?q?test=E2=9C=85:=20cover=20the=20operation=20?= =?UTF-8?q?log's=20truncation=20on=20a=20character=20boundary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SaveOperaLog shortens JsonResult to 100 characters before writing it. #944 changed that from a byte cut to a rune cut, but no test referenced it, so putting the byte cut back would turn nothing red. The test drives the real SaveOperaLog with 150 three-byte characters and reads the row back: the stored value must be valid UTF-8 and keep exactly 100 characters. With the byte cut restored it fails with an invalid trailing byte and 34 characters kept. --- app/admin/models/sys_opera_log_test.go | 53 ++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 app/admin/models/sys_opera_log_test.go diff --git a/app/admin/models/sys_opera_log_test.go b/app/admin/models/sys_opera_log_test.go new file mode 100644 index 00000000..291cdbf8 --- /dev/null +++ b/app/admin/models/sys_opera_log_test.go @@ -0,0 +1,53 @@ +package models + +import ( + "strings" + "testing" + "unicode/utf8" + + "github.com/glebarez/sqlite" + "github.com/go-admin-team/go-admin-core/v2/sdk" + "github.com/go-admin-team/go-admin-core/v2/storage/queue" + "gorm.io/gorm" +) + +// SaveOperaLog shortens JsonResult before writing it. Cutting at a byte offset +// splits a multi-byte character, and PostgreSQL and strict-mode MySQL reject the +// resulting string, so the row is dropped and the only trace is a log line. +func TestSaveOperaLogTruncatesOnCharacterBoundary(t *testing.T) { + const tenant = "opera-log-truncate-test" + db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{}) + if err != nil { + t.Fatalf("open db: %v", err) + } + if err := db.AutoMigrate(&SysOperaLog{}); err != nil { + t.Fatalf("migrate: %v", err) + } + sdk.Runtime.SetDbByTenant(tenant, db) + t.Cleanup(func() { sdk.Runtime.SetDbByTenant(tenant, nil) }) + + // 150 characters, 3 bytes each: byte 100 falls inside the 34th character. + long := strings.Repeat("中", 150) + + msg := &queue.Message{} + msg.SetValues(map[string]interface{}{"title": "t", "jsonResult": long}) + msg.SetPrefix(tenant) + if err := SaveOperaLog(msg); err != nil { + t.Fatalf("SaveOperaLog: %v", err) + } + + var rows []SysOperaLog + if err := db.Find(&rows).Error; err != nil { + t.Fatalf("read back: %v", err) + } + if len(rows) != 1 { + t.Fatalf("want 1 row written, got %d", len(rows)) + } + got := rows[0].JsonResult + if !utf8.ValidString(got) { + t.Errorf("stored JsonResult is not valid UTF-8: %q", got) + } + if n := utf8.RuneCountInString(got); n != 100 { + t.Errorf("want 100 characters kept, got %d", n) + } +} From 3afe083499b1404350ae15d8e075473cf5e5c1d7 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Thu, 1 Oct 2026 22:09:55 +0800 Subject: [PATCH 2/2] =?UTF-8?q?test=E2=9C=85:=20restore=20the=20tenant's?= =?UTF-8?q?=20previous=20database=20after=20the=20opera=20log=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sdk.Runtime is process-wide, so setting the tenant back to nil on cleanup could overwrite a mapping another test had installed. Save the previous value and put it back, as common/middleware/db_test.go does. --- app/admin/models/sys_opera_log_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/admin/models/sys_opera_log_test.go b/app/admin/models/sys_opera_log_test.go index 291cdbf8..4da298fe 100644 --- a/app/admin/models/sys_opera_log_test.go +++ b/app/admin/models/sys_opera_log_test.go @@ -23,8 +23,9 @@ func TestSaveOperaLogTruncatesOnCharacterBoundary(t *testing.T) { if err := db.AutoMigrate(&SysOperaLog{}); err != nil { t.Fatalf("migrate: %v", err) } + previous := sdk.Runtime.GetDbByTenant(tenant) sdk.Runtime.SetDbByTenant(tenant, db) - t.Cleanup(func() { sdk.Runtime.SetDbByTenant(tenant, nil) }) + t.Cleanup(func() { sdk.Runtime.SetDbByTenant(tenant, previous) }) // 150 characters, 3 bytes each: byte 100 falls inside the 34th character. long := strings.Repeat("中", 150)