110 lines
3.5 KiB
Go
110 lines
3.5 KiB
Go
package workbench
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"os"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/google/uuid"
|
||
|
|
"gorm.io/driver/postgres"
|
||
|
|
"gorm.io/gorm"
|
||
|
|
"gorm.io/gorm/logger"
|
||
|
|
)
|
||
|
|
|
||
|
|
// PostgreSQL distinguishes the conflict target from EXCLUDED; SQLite does not
|
||
|
|
// reject an unqualified status predicate, so this must execute on PostgreSQL.
|
||
|
|
func TestPostgresContentFeedbackAcceptanceUpsert(t *testing.T) {
|
||
|
|
dsn := os.Getenv("WORKBENCH_TEST_POSTGRES_DSN")
|
||
|
|
if dsn == "" {
|
||
|
|
t.Skip("WORKBENCH_TEST_POSTGRES_DSN is not set")
|
||
|
|
}
|
||
|
|
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
sqlDB, err := db.DB()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
defer sqlDB.Close()
|
||
|
|
schema := "wb_memory_test_" + strings.ReplaceAll(uuid.NewString(), "-", "")
|
||
|
|
if err := db.Exec("CREATE SCHEMA " + schema).Error; err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
if err := db.Exec("DROP SCHEMA " + schema + " CASCADE").Error; err != nil {
|
||
|
|
t.Error(err)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
testDSN := dsn + " search_path=" + schema
|
||
|
|
if strings.Contains(dsn, "://") {
|
||
|
|
separator := "?"
|
||
|
|
if strings.Contains(dsn, "?") {
|
||
|
|
separator = "&"
|
||
|
|
}
|
||
|
|
testDSN = dsn + separator + "search_path=" + schema
|
||
|
|
}
|
||
|
|
testDB, err := gorm.Open(postgres.Open(testDSN), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
testSQL, err := testDB.DB()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
defer testSQL.Close()
|
||
|
|
if err := testDB.AutoMigrate(&Resource{}, &Job{}, &Revision{}, &Event{}, &Consumption{}, &PendingMemory{}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
s, ctx, resource, job := feedbackHarnessWithService(t, NewService(testDB))
|
||
|
|
accept := func() PendingMemory {
|
||
|
|
t.Helper()
|
||
|
|
version := resource.Version
|
||
|
|
if err := s.executeTask(ctx, job, resource, ActionRequest{SegmentID: "segment"}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if err := s.save(ctx, resource, version); err != nil {
|
||
|
|
t.Fatalf("acceptance must commit with its pending feedback: %v", err)
|
||
|
|
}
|
||
|
|
var persisted Resource
|
||
|
|
if err := testDB.First(&persisted, "id = ?", resource.ID).Error; err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
data, err := decode[TaskData](persisted.Data)
|
||
|
|
if err != nil || persisted.Version != version+1 || data.Segments[0].Status != "accepted" {
|
||
|
|
t.Fatalf("acceptance was not persisted atomically: %+v %v", persisted, err)
|
||
|
|
}
|
||
|
|
var rows []PendingMemory
|
||
|
|
if err := testDB.Where("tenant_id = ? AND owner_id = ?", resource.TenantID, resource.OwnerID).Find(&rows).Error; err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if len(rows) != 1 || rows[0].Content != "开场先说结论,再给案例" {
|
||
|
|
t.Fatalf("feedback lost or duplicated: %+v", rows)
|
||
|
|
}
|
||
|
|
return rows[0]
|
||
|
|
}
|
||
|
|
first := accept()
|
||
|
|
if first.Status != "pending" {
|
||
|
|
t.Fatalf("initial acceptance did not create pending feedback: %+v", first)
|
||
|
|
}
|
||
|
|
if repeated := accept(); repeated.ID != first.ID || !repeated.UpdatedAt.Equal(first.UpdatedAt) {
|
||
|
|
t.Fatalf("repeat acceptance mutated an existing pending candidate: %+v", repeated)
|
||
|
|
}
|
||
|
|
for _, state := range []string{"invalidated", "accepted", "rejected"} {
|
||
|
|
stamp := time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC)
|
||
|
|
if err := testDB.Model(&PendingMemory{}).Where("id = ?", first.ID).
|
||
|
|
Updates(map[string]any{"status": state, "updated_at": stamp}).Error; err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
row := accept()
|
||
|
|
if state == "invalidated" {
|
||
|
|
if row.Status != "pending" || !row.UpdatedAt.After(stamp) {
|
||
|
|
t.Fatalf("reacceptance did not restore invalidated feedback: %+v", row)
|
||
|
|
}
|
||
|
|
} else if row.Status != state || !row.UpdatedAt.Equal(stamp) {
|
||
|
|
t.Fatalf("reacceptance changed explicit %s review: %+v", state, row)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|