Files
NianAIGC/backend/internal/postgres/layer_compositions_test.go

76 lines
3.4 KiB
Go

package postgres
import (
"context"
"errors"
"reflect"
"strings"
"testing"
"time"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/layercompositions"
)
func TestLayerCompositionAdapterScopesReadsAndUsesOptimisticSave(t *testing.T) {
now := time.Date(2026, 9, 2, 8, 0, 0, 0, time.UTC)
row := []any{"job-1", "owner", "base", true, []byte(`[{"id":"layer-1","assetId":"asset-1","name":"主体","visible":true,"box":[100,100,900,900],"rotation":0,"source":"provider"}]`), int64(2), now, now}
querier := &jobQuerier{rows: &jobRows{rows: [][]any{row}}}
database := NewDatabase(Config{Backend: BackendPostgres}, querier)
composition, found, err := database.FindLayerComposition(context.Background(), "owner", "job-1")
if err != nil || !found || composition.Version != 2 || len(composition.Layers) != 1 {
t.Fatalf("composition=%#v found=%v err=%v", composition, found, err)
}
if querier.query != FindLayerCompositionSQL || !reflect.DeepEqual(querier.args, []any{"owner", "job-1"}) {
t.Fatalf("query=%q args=%#v", querier.query, querier.args)
}
savedRow := append([]any(nil), row...)
savedRow[5] = int64(3)
querier.rows = &jobRows{rows: [][]any{savedRow}}
saved, err := database.SaveLayerComposition(context.Background(), layercompositions.Composition{
JobID: "job-1", OwnerID: "owner", BaseAssetID: "base", BaseVisible: true,
Layers: []layercompositions.Layer{{ID: "layer-1", AssetID: "asset-1", Name: "主体", Visible: true, Box: layercompositions.Box{100, 100, 900, 900}, Source: "provider"}},
}, 2)
if err != nil || saved.Version != 3 {
t.Fatalf("saved=%#v err=%v", saved, err)
}
if querier.query != SaveLayerCompositionSQL || len(querier.args) != 6 || querier.args[5] != int64(2) {
t.Fatalf("query=%q args=%#v", querier.query, querier.args)
}
for _, fragment := range []string{"WITH updated AS", "WHERE $6::bigint = 0", "version = seedream_layer_compositions.version + 1", "AND version = $6::bigint", "ON CONFLICT (job_id) DO NOTHING"} {
if !strings.Contains(SaveLayerCompositionSQL, fragment) {
t.Fatalf("upsert omits %q", fragment)
}
}
}
func TestLayerCompositionSaveSQLKeepsExistingVersionedUpdatesReachable(t *testing.T) {
// A top-level INSERT ... SELECT ... WHERE expectedVersion = 0 filters the
// source row before ON CONFLICT can run. In that shape every normal update
// (expectedVersion > 0) returns no row and is reported as a false conflict.
if strings.Contains(SaveLayerCompositionSQL, "SELECT $1::text, $2::text, $3::text, $4::boolean, $5::jsonb, 1, now(), now()\nWHERE $6::bigint = 0\nON CONFLICT") {
t.Fatal("versioned updates are unreachable because the INSERT source is filtered before ON CONFLICT")
}
for _, fragment := range []string{
"WITH updated AS",
"version = $6::bigint",
"$6::bigint > 0",
"WHERE $6::bigint = 0",
"UNION ALL",
} {
if !strings.Contains(SaveLayerCompositionSQL, fragment) {
t.Fatalf("save query does not preserve both update and create paths; missing %q", fragment)
}
}
}
func TestLayerCompositionAdapterReturnsConflictWhenVersionUpdateMatchesNoRow(t *testing.T) {
querier := &jobQuerier{rows: &jobRows{}}
database := NewDatabase(Config{Backend: BackendPostgres}, querier)
_, err := database.SaveLayerComposition(context.Background(), layercompositions.Composition{JobID: "job-1", OwnerID: "owner", BaseAssetID: "base", Layers: []layercompositions.Layer{}}, 4)
if !errors.Is(err, layercompositions.ErrConflict) {
t.Fatalf("err=%v", err)
}
}