Files
agent-desk-ng/internal/services/channel_policy_concurrency_test.go
mono 2f7a491666 feat: harden support, localize knowledge and add operator availability [skip ci]
Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.

Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.

Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.

Expected source tree: 0a4a7528e9.
2026-10-16 15:21:42 +00:00

119 lines
5.1 KiB
Go

package services
import (
"encoding/json"
"sync/atomic"
"testing"
"time"
"agent-desk/internal/models"
"agent-desk/internal/pkg/dto/request"
"agent-desk/internal/pkg/enums"
"agent-desk/internal/repositories"
"github.com/mlogclub/simple/sqls"
"gorm.io/gorm"
)
func TestChannelConfigCompareAndSwapRejectsStalePolicyAndSecretWrites(t *testing.T) {
for _, action := range []string{"editor", "secret-reset"} {
t.Run(action, func(t *testing.T) {
previousDB := sqls.DB()
db := setupChannelServiceTestDB(t)
t.Cleanup(func() { sqls.SetDB(previousDB) })
agent := createChannelServiceTestAgent(t, db, 1001)
channel, err := ChannelService.CreateChannel(request.CreateChannelRequest{ChannelType: enums.ChannelTypeWeb, AIAgentID: agent.ID, Name: "Policy channel", Status: int(enums.StatusOk), ConfigJSON: `{"requireSignedIdentity":false,"userTokenSecret":"test-old-secret"}`}, channelServiceTestOperator())
if err != nil {
t.Fatal(err)
}
var injected atomic.Bool
callbackName := "test:concurrent_channel_policy"
// Commit another writer after the stale service read, just before its SQL.
// Preserve updated_at deliberately: bytewise JSON CAS must also catch this.
if err := db.Callback().Update().Before("gorm:update").Register(callbackName, func(tx *gorm.DB) {
if tx.Statement.Schema == nil || tx.Statement.Schema.Table != "t_channel" || !injected.CompareAndSwap(false, true) {
return
}
cfg, parseErr := ChannelService.ParseWebChannelConfig(channel.ConfigJSON)
if parseErr != nil {
tx.AddError(parseErr)
return
}
cfg.RequireSignedIdentity = true
cfg.UserTokenSecret = "test-new-secret"
encoded, encodeErr := json.Marshal(cfg)
if encodeErr != nil {
tx.AddError(encodeErr)
return
}
if writeErr := db.Session(&gorm.Session{NewDB: true}).Model(&models.Channel{}).Where("id = ?", channel.ID).Updates(map[string]any{"config_json": string(encoded), "updated_at": channel.UpdatedAt}).Error; writeErr != nil {
tx.AddError(writeErr)
}
}); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = db.Callback().Update().Remove(callbackName) })
switch action {
case "editor":
err = ChannelService.UpdateChannel(request.UpdateChannelRequest{ID: channel.ID, CreateChannelRequest: request.CreateChannelRequest{ChannelType: enums.ChannelTypeWeb, AIAgentID: agent.ID, Name: "Old editor title", Status: int(enums.StatusOk), ConfigJSON: `{"title":"old editor"}`}}, channelServiceTestOperator())
case "secret-reset":
var secret string
secret, err = ChannelService.ResetUserTokenSecret(channel.ID, channelServiceTestOperator())
if secret != "" {
t.Fatal("failed reset returned an uncommitted secret")
}
}
if !injected.Load() || err == nil {
t.Fatal("stale writer was not rejected")
}
current := ChannelService.Get(channel.ID)
cfg, parseErr := ChannelService.ParseWebChannelConfig(current.ConfigJSON)
if parseErr != nil || !cfg.RequireSignedIdentity || cfg.UserTokenSecret != "test-new-secret" {
t.Fatal("stale writer replaced the concurrent policy/secret")
}
})
}
}
func TestChannelConfigCompareAndSwapUsesExactBytes(t *testing.T) {
db, channel := setupIdentitySecurityDB(t)
channel.ConfigJSON = `{"userTokenSecret":"CaseSensitive"}`
if err := db.Model(channel).Updates(map[string]any{"config_json": channel.ConfigJSON, "updated_at": time.Now()}).Error; err != nil {
t.Fatal(err)
}
expected := *channel
if err := db.Model(channel).UpdateColumn("config_json", `{"userTokenSecret":"casesensitive"}`).Error; err != nil {
t.Fatal(err)
}
updated, err := repositories.ChannelRepository.UpdatesConfigIfUnchanged(db, &expected, map[string]any{"config_json": `{"requireSignedIdentity":false}`})
if err != nil {
t.Fatal(err)
}
if updated {
t.Fatal("case-sensitive secret change was not treated as a conflict")
}
}
func TestChannelConfigCompareAndSwapAllowsCurrentEditorAndReset(t *testing.T) {
previousDB := sqls.DB()
db := setupChannelServiceTestDB(t)
t.Cleanup(func() { sqls.SetDB(previousDB) })
agent := createChannelServiceTestAgent(t, db, 1001)
channel, err := ChannelService.CreateChannel(request.CreateChannelRequest{ChannelType: enums.ChannelTypeWeb, AIAgentID: agent.ID, Name: "Current policy", Status: int(enums.StatusOk), ConfigJSON: `{"requireSignedIdentity":true}`}, channelServiceTestOperator())
if err != nil {
t.Fatal(err)
}
err = ChannelService.UpdateChannel(request.UpdateChannelRequest{ID: channel.ID, CreateChannelRequest: request.CreateChannelRequest{ChannelType: enums.ChannelTypeWeb, AIAgentID: agent.ID, Name: "Current policy edited", Status: int(enums.StatusOk), ConfigJSON: `{"title":"Updated title"}`}}, channelServiceTestOperator())
if err != nil {
t.Fatal(err)
}
secret, err := ChannelService.ResetUserTokenSecret(channel.ID, channelServiceTestOperator())
if err != nil || secret == "" {
t.Fatal("current secret reset failed")
}
cfg, err := ChannelService.ParseWebChannelConfig(ChannelService.Get(channel.ID).ConfigJSON)
if err != nil || !cfg.RequireSignedIdentity || cfg.UserTokenSecret != secret {
t.Fatal("current update/reset lost policy or returned unsaved secret")
}
}