Files
yawan f7fc992f57
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
2026-09-22 16:19:06 +08:00

344 lines
20 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package repository
import (
"context"
"strings"
"testing"
"time"
"github.com/Tencent/WeKnora/internal/accesspolicy"
"github.com/Tencent/WeKnora/internal/types"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func setupDigitalHumanTest(t *testing.T) (*DigitalHumanRepository, *gorm.DB, types.DigitalHumanScope, *types.DigitalHuman) {
t.Helper()
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
require.NoError(t, err)
sqlDB, err := db.DB()
require.NoError(t, err)
sqlDB.SetMaxOpenConns(1)
t.Cleanup(func() { sqlDB.Close() })
require.NoError(t, db.AutoMigrate(&types.CustomAgent{}, &types.DigitalHuman{}, &types.DigitalHumanVersion{}, &types.DigitalHumanBinding{}, &types.DigitalHumanJob{}))
require.NoError(t, db.Exec("CREATE TABLE users (id TEXT PRIMARY KEY, username TEXT, deleted_at DATETIME)").Error)
require.NoError(t, db.Exec("CREATE TABLE im_channels (id TEXT PRIMARY KEY, agent_id TEXT, tenant_id INTEGER, enabled BOOLEAN, updated_at DATETIME, deleted_at DATETIME)").Error)
require.NoError(t, db.Exec("INSERT INTO users (id, username) VALUES (?, ?)", "owner", "Teacher Owner").Error)
scope := types.DigitalHumanScope{TenantID: 7, OwnerID: "owner"}
human := &types.DigitalHuman{ID: uuid.NewString(), TenantID: scope.TenantID, OwnerID: scope.OwnerID, Name: "Teacher", PrimaryAgentID: uuid.NewString(), LatestVersion: 1, Status: "draft", Revision: 1}
repo := NewDigitalHumanRepository(db)
require.NoError(t, repo.Create(context.Background(), human, map[string]string{"tone": "measured"}))
return repo, db, scope, human
}
func TestDigitalHumanProgressRenewsLeaseAndFencesExpiredOrForeignJobs(t *testing.T) {
repo, db, scope, human := setupDigitalHumanTest(t)
ctx := context.Background()
job := &types.DigitalHumanJob{ID: "job", TenantID: scope.TenantID, OwnerID: scope.OwnerID, DigitalHumanID: human.ID, Status: "running", ExpiresAt: time.Now().Add(time.Minute)}
require.NoError(t, repo.StartJob(ctx, scope, human.ID, human.Revision, job))
require.NoError(t, repo.JobProgress(ctx, scope, job.ID, 40, "batch 4/10"))
stored, err := repo.Job(ctx, scope, human.ID, job.ID)
require.NoError(t, err)
require.Greater(t, stored.ExpiresAt.Sub(time.Now()), 9*time.Minute)
foreign := scope
foreign.OwnerID = "other-owner"
require.ErrorIs(t, repo.JobProgress(ctx, foreign, job.ID, 50, "foreign"), types.ErrDigitalHumanConflict)
require.NoError(t, db.Model(&types.DigitalHumanJob{}).Where("id = ?", job.ID).Update("expires_at", time.Now().Add(-time.Minute)).Error)
require.ErrorIs(t, repo.JobProgress(ctx, scope, job.ID, 60, "late"), types.ErrDigitalHumanConflict)
}
func TestDigitalHumanCheckpointIsPrivateOwnerScopedAndFenced(t *testing.T) {
repo, db, scope, human := setupDigitalHumanTest(t)
ctx := context.Background()
key := strings.Repeat("a", 64)
checkpoint := &types.DigitalHumanDistillCheckpoint{CompletedBatches: 1, TotalBatches: 3, Persona: map[string]string{"tone": "private draft"}}
job := &types.DigitalHumanJob{ID: "checkpoint-job", TenantID: scope.TenantID, OwnerID: scope.OwnerID, DigitalHumanID: human.ID, Status: "running", ExpiresAt: time.Now().Add(time.Minute)}
require.NoError(t, repo.StartJob(ctx, scope, human.ID, human.Revision, job))
require.NoError(t, repo.SaveDistillCheckpoint(ctx, scope, human.ID, job.ID, key, checkpoint))
foreign := scope
foreign.OwnerID = "other"
require.Error(t, repo.SaveDistillCheckpoint(ctx, foreign, human.ID, job.ID, key, checkpoint))
require.ErrorIs(t, repo.SaveDistillCheckpoint(ctx, scope, human.ID, job.ID, strings.Repeat("b", 64), checkpoint), types.ErrDigitalHumanConflict)
require.ErrorIs(t, repo.SaveDistillCheckpoint(ctx, scope, human.ID, job.ID, key, &types.DigitalHumanDistillCheckpoint{CompletedBatches: 0, TotalBatches: 3}), types.ErrDigitalHumanConflict)
require.NoError(t, db.Model(&types.DigitalHumanJob{}).Where("id = ?", job.ID).Update("expires_at", time.Now().Add(-time.Minute)).Error)
require.ErrorIs(t, repo.SaveDistillCheckpoint(ctx, scope, human.ID, job.ID, key, checkpoint), types.ErrDigitalHumanConflict)
require.NoError(t, repo.RecoverExpiredJobs(ctx, scope))
found, err := repo.DistillCheckpoint(ctx, scope, human.ID, key)
require.NoError(t, err)
require.Equal(t, checkpoint, found)
for _, lookup := range []struct {
scope types.DigitalHumanScope
human, fingerprint string
}{
{foreign, human.ID, key},
{types.DigitalHumanScope{TenantID: 8, OwnerID: scope.OwnerID}, human.ID, key},
{scope, "another-human", key},
{scope, human.ID, strings.Repeat("c", 64)},
} {
found, err := repo.DistillCheckpoint(ctx, lookup.scope, lookup.human, lookup.fingerprint)
require.NoError(t, err)
require.Nil(t, found)
}
detail, err := repo.Get(ctx, scope, human.ID)
require.NoError(t, err)
newJob := &types.DigitalHumanJob{ID: "new-job", TenantID: scope.TenantID, OwnerID: scope.OwnerID, DigitalHumanID: human.ID, Status: "running", ExpiresAt: time.Now().Add(time.Minute)}
require.NoError(t, repo.StartJob(ctx, scope, human.ID, detail.Revision, newJob))
// Even a stale worker with a stale local lease may not change old retry state.
require.ErrorIs(t, repo.SaveDistillCheckpoint(ctx, scope, human.ID, job.ID, key, checkpoint), types.ErrDigitalHumanConflict)
require.NoError(t, repo.SaveDistillCheckpoint(ctx, scope, human.ID, newJob.ID, key, checkpoint))
}
func TestDigitalHumanDeletionCommitsChannelsTogether(t *testing.T) {
for _, rollback := range []bool{false, true} {
t.Run(map[bool]string{false: "commit", true: "rollback"}[rollback], func(t *testing.T) {
repo, db, scope, human := setupDigitalHumanTest(t)
for _, row := range []struct {
id, agent string
tenant uint64
}{
{"owned", human.PrimaryAgentID, scope.TenantID},
{"other-agent", "other-agent", scope.TenantID},
{"other-tenant", human.PrimaryAgentID, scope.TenantID + 1},
} {
require.NoError(t, db.Exec("INSERT INTO im_channels (id, agent_id, tenant_id, enabled) VALUES (?, ?, ?, true)", row.id, row.agent, row.tenant).Error)
}
if rollback {
require.NoError(t, db.Exec("CREATE TRIGGER reject_digital_human_delete BEFORE UPDATE OF deleted_at ON digital_humans BEGIN SELECT RAISE(ABORT, 'simulated write failure'); END").Error)
}
ids, err := repo.DeleteWithIMChannels(context.Background(), scope, human.ID)
var channels int64
require.NoError(t, db.Table("im_channels").Where("deleted_at IS NULL AND enabled = true").Count(&channels).Error)
var agents int64
require.NoError(t, db.Model(&types.CustomAgent{}).Where("id = ?", human.PrimaryAgentID).Count(&agents).Error)
if rollback {
require.Error(t, err)
require.Empty(t, ids)
require.Equal(t, int64(3), channels)
require.Equal(t, int64(1), agents)
} else {
require.NoError(t, err)
require.Equal(t, []string{"owned"}, ids)
require.Equal(t, int64(2), channels)
require.Zero(t, agents)
}
})
}
}
func TestDigitalHumanOwnerIsolationAndAdminRead(t *testing.T) {
repo, db, scope, human := setupDigitalHumanTest(t)
ctx := context.Background()
var agent types.CustomAgent
require.NoError(t, db.First(&agent, "id = ?", human.PrimaryAgentID).Error)
require.Equal(t, human.Name, agent.Name)
require.Equal(t, scope.OwnerID, agent.CreatedBy)
other := types.DigitalHumanScope{TenantID: scope.TenantID, OwnerID: "other"}
_, err := repo.Get(ctx, other, human.ID)
require.ErrorIs(t, err, types.ErrDigitalHumanNotFound)
other.ReadAll = true
detail, err := repo.Get(ctx, other, human.ID)
require.NoError(t, err)
require.Len(t, detail.Versions, 1)
require.Len(t, detail.Bindings, 1)
require.Equal(t, "Teacher Owner", detail.CreatorName)
listed, err := repo.List(ctx, other, 0, 100)
require.NoError(t, err)
require.Len(t, listed, 1)
require.Equal(t, "Teacher Owner", listed[0].CreatorName)
require.ErrorIs(t, repo.Delete(ctx, other, human.ID), types.ErrDigitalHumanNotFound)
require.ErrorIs(t, repo.Review(ctx, other, human.ID, 1, true, ""), types.ErrDigitalHumanNotFound)
other.TenantID++
_, err = repo.Get(ctx, other, human.ID)
require.ErrorIs(t, err, types.ErrDigitalHumanNotFound)
}
func TestDigitalHumanReviewBindingsAndDeletionPreserveAgentEdits(t *testing.T) {
repo, db, scope, human := setupDigitalHumanTest(t)
ctx := context.Background()
extra := types.CustomAgent{ID: uuid.NewString(), TenantID: scope.TenantID, CreatedBy: scope.OwnerID, Name: "Content", Config: types.CustomAgentConfig{SystemPrompt: "Original task rules"}}
require.NoError(t, db.Create(&extra).Error)
require.NoError(t, repo.Bind(ctx, scope, human.ID, extra.ID))
require.NoError(t, repo.Review(ctx, scope, human.ID, 1, true, "approved"))
require.NoError(t, db.First(&extra, "id = ?", extra.ID).Error)
require.Contains(t, extra.Config.SystemPrompt, "measured")
extra.Config.SystemPrompt = "Revised task rules\n" + extra.Config.SystemPrompt + "\nKeep continuity"
require.NoError(t, db.Save(&extra).Error)
h, err := repo.Get(ctx, scope, human.ID)
require.NoError(t, err)
require.NoError(t, repo.Update(ctx, scope, human.ID, h.Revision, h.Name, h.Description, h.Avatar, h.ModelID, map[string]string{"tone": "clear"}))
require.NoError(t, repo.Review(ctx, scope, human.ID, 2, true, ""))
require.NoError(t, db.First(&extra, "id = ?", extra.ID).Error)
require.Contains(t, extra.Config.SystemPrompt, "Revised task rules")
require.Contains(t, extra.Config.SystemPrompt, "Keep continuity")
require.NotContains(t, extra.Config.SystemPrompt, "measured")
require.Equal(t, 1, strings.Count(extra.Config.SystemPrompt, ":start -->"))
require.NoError(t, repo.Delete(ctx, scope, human.ID))
require.NoError(t, db.First(&extra, "id = ?", extra.ID).Error)
require.NotContains(t, extra.Config.SystemPrompt, "weknora:digital-human:")
require.Contains(t, extra.Config.SystemPrompt, "Keep continuity")
var count int64
require.NoError(t, db.Model(&types.CustomAgent{}).Where("id = ?", human.PrimaryAgentID).Count(&count).Error)
require.Zero(t, count)
require.NoError(t, db.Model(&types.DigitalHumanBinding{}).Where("digital_human_id = ?", human.ID).Count(&count).Error)
require.Zero(t, count)
}
func TestDigitalHumanAgentCopyKeepsLiveBinding(t *testing.T) {
repo, db, scope, human := setupDigitalHumanTest(t)
ctx := context.WithValue(context.Background(), types.TenantIDContextKey, scope.TenantID)
ctx = context.WithValue(ctx, types.UserIDContextKey, scope.OwnerID)
require.NoError(t, repo.Review(ctx, scope, human.ID, 1, true, "approved"))
var source types.CustomAgent
require.NoError(t, db.First(&source, "id = ?", human.PrimaryAgentID).Error)
copy := source
copy.ID, copy.Name = uuid.NewString(), "Copied persona"
agents := &customAgentRepository{db: db}
require.NoError(t, agents.CreateAgentCopy(ctx, source.ID, &copy))
require.NoError(t, agents.CheckDigitalHumanAgentExecution(ctx, &copy))
detail, err := repo.Get(ctx, scope, human.ID)
require.NoError(t, err)
require.Len(t, detail.Bindings, 2)
require.NoError(t, repo.Update(ctx, scope, human.ID, detail.Revision, detail.Name, detail.Description, detail.Avatar, detail.ModelID, map[string]string{"tone": "new voice"}))
require.NoError(t, repo.Review(ctx, scope, human.ID, 2, true, "approved"))
require.NoError(t, db.First(&copy, "id = ?", copy.ID).Error)
require.Contains(t, copy.Config.SystemPrompt, "new voice")
require.NoError(t, agents.CheckDigitalHumanAgentExecution(ctx, &copy))
staleCopy := source
staleCopy.ID = uuid.NewString()
require.ErrorIs(t, agents.CreateAgentCopy(ctx, source.ID, &staleCopy), types.ErrDigitalHumanConflict)
var count int64
require.NoError(t, db.Model(&types.CustomAgent{}).Where("id = ?", staleCopy.ID).Count(&count).Error)
require.Zero(t, count)
require.NoError(t, repo.Unbind(ctx, scope, human.ID, copy.ID))
require.Error(t, agents.CheckDigitalHumanAgentExecution(ctx, &copy), "saved profile must stop executing after unbind")
}
func TestSharedDigitalHumanCanBeMountedWithoutChangingSourceOrOtherUsers(t *testing.T) {
repo, db, owner, human := setupDigitalHumanTest(t)
ctx := accesspolicy.WithSnapshot(context.Background(), &accesspolicy.Snapshot{Publications: map[string]bool{"digital-humans:" + human.ID: true}})
consumer := types.DigitalHumanScope{TenantID: owner.TenantID, OwnerID: "consumer"}
for _, agent := range []types.CustomAgent{
{ID: "consumer-agent", TenantID: owner.TenantID, CreatedBy: consumer.OwnerID, Name: "Consumer", Config: types.CustomAgentConfig{SystemPrompt: "Consumer task"}},
{ID: "other-agent", TenantID: owner.TenantID, CreatedBy: "other", Name: "Other"},
{ID: "foreign-agent", TenantID: owner.TenantID + 1, CreatedBy: consumer.OwnerID, Name: "Foreign"},
} {
require.NoError(t, db.Create(&agent).Error)
}
require.NoError(t, repo.Review(ctx, owner, human.ID, 1, true, ""))
require.NoError(t, repo.Bind(ctx, consumer, human.ID, "consumer-agent"))
require.ErrorIs(t, repo.Bind(ctx, consumer, human.ID, "other-agent"), types.ErrDigitalHumanNotFound)
require.ErrorIs(t, repo.Bind(ctx, consumer, human.ID, "foreign-agent"), types.ErrDigitalHumanNotFound)
require.ErrorIs(t, repo.Delete(ctx, consumer, human.ID), types.ErrDigitalHumanNotFound)
detail, err := repo.Get(ctx, consumer, human.ID)
require.NoError(t, err)
require.Len(t, detail.Bindings, 1, "recipients only see bindings for their own Agents")
require.Equal(t, "consumer-agent", detail.Bindings[0].AgentID)
var agent types.CustomAgent
require.NoError(t, db.First(&agent, "id = ?", "consumer-agent").Error)
require.Contains(t, agent.Config.SystemPrompt, "measured")
agentRepo := &customAgentRepository{db: db}
consumerContext := context.WithValue(ctx, types.TenantIDContextKey, owner.TenantID)
consumerContext = context.WithValue(consumerContext, types.UserIDContextKey, consumer.OwnerID)
visible, err := agentRepo.FilterDigitalHumanAgents(consumerContext, []*types.CustomAgent{&agent})
require.NoError(t, err)
require.Len(t, visible, 1)
require.NotNil(t, visible[0].DigitalHuman)
require.Equal(t, human.ID, visible[0].DigitalHuman.ID)
require.Equal(t, human.Name, visible[0].DigitalHuman.Name)
require.Equal(t, 1, visible[0].DigitalHuman.AppliedVersion)
require.ErrorContains(t, agentRepo.CheckDigitalHumanAgentCopy(consumerContext, agent.ID, owner.TenantID), "共享数字人")
require.NoError(t, agentRepo.CheckDigitalHumanAgentExecution(consumerContext, &agent))
savedSnapshot := agent
revokedExecution := accesspolicy.WithSnapshot(consumerContext, &accesspolicy.Snapshot{Publications: map[string]bool{"digital-humans:" + human.ID: false}})
require.ErrorContains(t, agentRepo.CheckDigitalHumanAgentExecution(revokedExecution, &savedSnapshot), "授权已撤销")
foreignExecution := context.WithValue(consumerContext, types.TenantIDContextKey, owner.TenantID+1)
require.Error(t, agentRepo.CheckDigitalHumanAgentExecution(foreignExecution, &savedSnapshot))
adminContext := context.WithValue(consumerContext, types.TenantRoleContextKey, types.TenantRoleAdmin)
require.NoError(t, agentRepo.CheckDigitalHumanAgentCopy(adminContext, agent.ID, owner.TenantID))
// Owner publication updates every installed binding, preserving its owner.
require.NoError(t, repo.Update(ctx, owner, human.ID, detail.Revision, human.Name, "", "", "", map[string]string{"tone": "revised"}))
require.NoError(t, repo.Review(ctx, owner, human.ID, 2, true, ""))
require.NoError(t, db.First(&agent, "id = ?", "consumer-agent").Error)
require.Contains(t, agent.Config.SystemPrompt, "revised")
require.Equal(t, consumer.OwnerID, agent.CreatedBy)
// Revocation must still permit removing a persona from one's own Agent.
revoked := accesspolicy.WithSnapshot(ctx, &accesspolicy.Snapshot{})
require.NoError(t, repo.Unbind(revoked, consumer, human.ID, agent.ID))
require.ErrorContains(t, agentRepo.CheckDigitalHumanAgentExecution(consumerContext, &savedSnapshot), "解绑")
require.NoError(t, db.First(&agent, "id = ?", "consumer-agent").Error)
require.Equal(t, "Consumer task", agent.Config.SystemPrompt)
require.ErrorIs(t, repo.Bind(revoked, consumer, human.ID, agent.ID), types.ErrDigitalHumanNotFound)
require.NoError(t, repo.Bind(ctx, consumer, human.ID, agent.ID))
require.NoError(t, repo.Delete(ctx, owner, human.ID))
require.NoError(t, db.First(&agent, "id = ?", "consumer-agent").Error)
require.Equal(t, "Consumer task", agent.Config.SystemPrompt)
var count int64
require.NoError(t, db.Model(&types.DigitalHumanBinding{}).Where("digital_human_id = ?", human.ID).Count(&count).Error)
require.Zero(t, count)
}
func TestDigitalHumanJobRecoveryFencesOldWorker(t *testing.T) {
repo, _, scope, human := setupDigitalHumanTest(t)
ctx := context.Background()
job := &types.DigitalHumanJob{ID: uuid.NewString(), TenantID: scope.TenantID, OwnerID: scope.OwnerID, DigitalHumanID: human.ID, Status: "running", ExpiresAt: time.Now().Add(-time.Minute)}
require.NoError(t, repo.StartJob(ctx, scope, human.ID, human.Revision, job))
require.ErrorIs(t, repo.StartJob(ctx, scope, human.ID, human.Revision, job), types.ErrDigitalHumanConflict)
require.NoError(t, repo.RecoverExpiredJobs(ctx, scope))
h, err := repo.Get(ctx, scope, human.ID)
require.NoError(t, err)
require.Empty(t, h.ProcessingJobID)
stored, err := repo.Job(ctx, scope, human.ID, job.ID)
require.NoError(t, err)
require.Equal(t, "failed", stored.Status)
require.ErrorIs(t, repo.FinishJob(ctx, scope, human.ID, job.ID, &types.DigitalHumanVersion{Persona: map[string]string{"tone": "stale"}}, ""), types.ErrDigitalHumanConflict)
job2 := &types.DigitalHumanJob{ID: uuid.NewString(), TenantID: scope.TenantID, OwnerID: scope.OwnerID, DigitalHumanID: human.ID, Status: "running", ExpiresAt: time.Now().Add(time.Minute)}
require.NoError(t, repo.StartJob(ctx, scope, human.ID, h.Revision, job2))
require.NoError(t, repo.FinishJob(ctx, scope, human.ID, job2.ID, &types.DigitalHumanVersion{Persona: map[string]string{"tone": "fresh"}}, ""))
h, err = repo.Get(ctx, scope, human.ID)
require.NoError(t, err)
require.Equal(t, 2, h.LatestVersion)
require.Equal(t, "fresh", h.Versions[0].Persona["tone"])
}
func TestImportedDigitalHumanKeepsStyleUntilNewVersionReviewed(t *testing.T) {
repo, db, scope, human := setupDigitalHumanTest(t)
ctx := context.Background()
persona := map[string]string{"persona_name": "Legacy teacher", "tone": "direct", "taboo": "no jargon", "vocab": "hello", "identity": "teacher", "interaction": "ask questions"}
require.NoError(t, db.Model(&types.DigitalHumanVersion{}).Where("digital_human_id = ?", human.ID).
Updates(&types.DigitalHumanVersion{Status: "imported", Persona: persona}).Error)
require.NoError(t, db.Model(human).Updates(map[string]any{"status": "active", "active_version": 1}).Error)
extra := types.CustomAgent{ID: uuid.NewString(), TenantID: scope.TenantID, CreatedBy: scope.OwnerID,
Name: "Imported style consumer", Config: types.CustomAgentConfig{SystemPrompt: "Keep current task rules"}}
require.NoError(t, db.Create(&extra).Error)
require.NoError(t, repo.Bind(ctx, scope, human.ID, extra.ID))
require.NoError(t, db.First(&extra, "id = ?", extra.ID).Error)
require.Contains(t, extra.Config.SystemPrompt, "旧配置迁入数字人(未重新蒸馏)")
require.NotContains(t, extra.Config.SystemPrompt, "已审核数字人")
for _, value := range persona {
require.Contains(t, extra.Config.SystemPrompt, value)
}
require.ErrorIs(t, repo.Review(ctx, scope, human.ID, 1, true, ""), types.ErrDigitalHumanConflict)
detail, err := repo.Get(ctx, scope, human.ID)
require.NoError(t, err)
require.Nil(t, detail.Versions[0].ReviewedAt)
require.NoError(t, repo.Update(ctx, scope, human.ID, detail.Revision, human.Name, "", "", "", map[string]string{"tone": "new tone"}))
require.NoError(t, db.First(&extra, "id = ?", extra.ID).Error)
require.Contains(t, extra.Config.SystemPrompt, "tone:direct")
require.NotContains(t, extra.Config.SystemPrompt, "new tone")
require.NoError(t, repo.Review(ctx, scope, human.ID, 2, false, "revise"))
detail, err = repo.Get(ctx, scope, human.ID)
require.NoError(t, err)
require.Equal(t, 1, detail.ActiveVersion)
require.Equal(t, "active", detail.Status)
require.NoError(t, repo.Update(ctx, scope, human.ID, detail.Revision, human.Name, "", "", "", map[string]string{"tone": "reviewed replacement"}))
require.NoError(t, repo.Review(ctx, scope, human.ID, 3, true, ""))
require.NoError(t, db.First(&extra, "id = ?", extra.ID).Error)
require.Contains(t, extra.Config.SystemPrompt, "已审核数字人")
require.Contains(t, extra.Config.SystemPrompt, "Keep current task rules")
require.Contains(t, extra.Config.SystemPrompt, "reviewed replacement")
require.NotContains(t, extra.Config.SystemPrompt, "tone:direct")
require.Equal(t, 1, strings.Count(extra.Config.SystemPrompt, ":start -->"))
}