Files
photoprism/internal/service/cluster/registry/registry_clientid_reuse_test.go
2025-09-25 23:09:52 +02:00

79 lines
2.9 KiB
Go

package registry
import (
"testing"
"github.com/stretchr/testify/assert"
cfg "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/service/cluster"
"github.com/photoprism/photoprism/pkg/rnd"
)
// When both a conflicting UUID and an existing ClientID are provided, the UUID-first
// rule prevents hijacking: the update applies to the UUID's row and does not move
// the ClientID from its original node.
func TestClientRegistry_ClientIDReuse_CannotHijackExistingUUID(t *testing.T) {
c := cfg.NewMinimalTestConfigWithDb("cluster-registry-cid-hijack", t.TempDir())
defer c.CloseDb()
r, _ := NewClientRegistryWithConfig(c)
// Seed two independent nodes
a := &Node{Node: cluster.Node{UUID: rnd.UUIDv7(), Name: "pp-a", Role: "instance"}}
b := &Node{Node: cluster.Node{UUID: rnd.UUIDv7(), Name: "pp-b", Role: "service"}}
assert.NoError(t, r.Put(a))
assert.NoError(t, r.Put(b))
// Attempt to update UUID=b while passing ClientID of a
assert.NoError(t, r.Put(&Node{Node: cluster.Node{UUID: b.UUID, ClientID: a.ClientID, Role: "service"}}))
// a stays attached to its original UUID and ClientID
gotA, err := r.FindByNodeUUID(a.UUID)
assert.NoError(t, err)
if assert.NotNil(t, gotA) {
assert.Equal(t, a.ClientID, gotA.ClientID)
assert.True(t, rnd.IsUUID(gotA.UUID))
assert.True(t, rnd.IsUID(gotA.ClientID, entity.ClientUID))
}
// b remains the same client row (not replaced by a)
gotB, err := r.FindByNodeUUID(b.UUID)
assert.NoError(t, err)
if assert.NotNil(t, gotB) {
assert.Equal(t, b.ClientID, gotB.ClientID)
assert.True(t, rnd.IsUUID(gotB.UUID))
assert.True(t, rnd.IsUID(gotB.ClientID, entity.ClientUID))
}
}
// If a target UUID does not exist yet, providing an existing ClientID with a new UUID
// migrates the row to the new UUID. This mirrors restore flows where a node's ClientID
// is reused for a regenerated or reassigned UUID.
func TestClientRegistry_ClientIDReuse_ChangesUUIDWhenTargetMissing(t *testing.T) {
c := cfg.NewMinimalTestConfigWithDb("cluster-registry-cid-move", t.TempDir())
defer c.CloseDb()
r, _ := NewClientRegistryWithConfig(c)
// Seed one node
a := &Node{Node: cluster.Node{UUID: rnd.UUIDv7(), Name: "pp-x", Role: "instance"}}
assert.NoError(t, r.Put(a))
// Move the row to a new UUID by referencing the same ClientID and a new UUID
newUUID := rnd.UUIDv7()
assert.NoError(t, r.Put(&Node{Node: cluster.Node{UUID: newUUID, ClientID: a.ClientID}}))
// Old UUID no longer resolves
_, err := r.FindByNodeUUID(a.UUID)
assert.Error(t, err)
// New UUID points to the same client row (ClientID unchanged)
got, err := r.FindByNodeUUID(newUUID)
assert.NoError(t, err)
if assert.NotNil(t, got) {
assert.Equal(t, a.ClientID, got.ClientID)
assert.Equal(t, newUUID, got.UUID)
assert.True(t, rnd.IsUUID(got.UUID))
assert.True(t, rnd.IsUID(got.ClientID, entity.ClientUID))
}
}