Files
goclaw/internal/http/agents_gateway_operator_bootstrap_test.go

657 lines
21 KiB
Go

package http
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/google/uuid"
"github.com/nextlevelbuilder/goclaw/internal/store"
)
type gatewayOperatorSecureCLIStore struct {
binaries []store.SecureCLIBinary
created *store.SecureCLIBinary
updated map[uuid.UUID]map[string]any
}
func (s *gatewayOperatorSecureCLIStore) Create(_ context.Context, b *store.SecureCLIBinary) error {
cp := *b
if cp.ID == uuid.Nil {
cp.ID = store.GenNewID()
b.ID = cp.ID
}
s.created = &cp
s.binaries = append(s.binaries, cp)
return nil
}
func (s *gatewayOperatorSecureCLIStore) Get(context.Context, uuid.UUID) (*store.SecureCLIBinary, error) {
return nil, sql.ErrNoRows
}
func (s *gatewayOperatorSecureCLIStore) Update(_ context.Context, id uuid.UUID, updates map[string]any) error {
if s.updated == nil {
s.updated = map[uuid.UUID]map[string]any{}
}
cp := map[string]any{}
for k, v := range updates {
cp[k] = v
}
s.updated[id] = cp
for i := range s.binaries {
if s.binaries[i].ID != id {
continue
}
for k, v := range updates {
switch k {
case "binary_path":
if path, ok := v.(string); ok {
s.binaries[i].BinaryPath = &path
}
case "description":
if description, ok := v.(string); ok {
s.binaries[i].Description = description
}
case "deny_args":
if raw, ok := v.(json.RawMessage); ok {
s.binaries[i].DenyArgs = raw
}
case "deny_verbose":
if raw, ok := v.(json.RawMessage); ok {
s.binaries[i].DenyVerbose = raw
}
case "timeout_seconds":
if timeoutSeconds, ok := v.(int); ok {
s.binaries[i].TimeoutSeconds = timeoutSeconds
}
case "tips":
if tips, ok := v.(string); ok {
s.binaries[i].Tips = tips
}
case "is_global":
if isGlobal, ok := v.(bool); ok {
s.binaries[i].IsGlobal = isGlobal
}
case "enabled":
if enabled, ok := v.(bool); ok {
s.binaries[i].Enabled = enabled
}
case "adapter_name":
if adapterName, ok := v.(*string); ok {
s.binaries[i].AdapterName = adapterName
} else {
s.binaries[i].AdapterName = nil
}
}
}
return nil
}
return nil
}
func (s *gatewayOperatorSecureCLIStore) Delete(context.Context, uuid.UUID) error { return nil }
func (s *gatewayOperatorSecureCLIStore) List(context.Context) ([]store.SecureCLIBinary, error) {
out := make([]store.SecureCLIBinary, len(s.binaries))
copy(out, s.binaries)
return out, nil
}
func (s *gatewayOperatorSecureCLIStore) LookupByBinary(context.Context, string, *uuid.UUID, string) (*store.SecureCLIBinary, error) {
return nil, sql.ErrNoRows
}
func (s *gatewayOperatorSecureCLIStore) ListEnabled(context.Context) ([]store.SecureCLIBinary, error) {
return nil, nil
}
func (s *gatewayOperatorSecureCLIStore) ListForAgent(context.Context, uuid.UUID) ([]store.SecureCLIBinary, error) {
return nil, nil
}
func (s *gatewayOperatorSecureCLIStore) IsRegisteredBinary(context.Context, string) (bool, error) {
return false, nil
}
func (s *gatewayOperatorSecureCLIStore) GetUserCredentials(context.Context, uuid.UUID, string) (*store.SecureCLIUserCredential, error) {
return nil, sql.ErrNoRows
}
func (s *gatewayOperatorSecureCLIStore) SetUserCredentials(context.Context, uuid.UUID, string, []byte) error {
return nil
}
func (s *gatewayOperatorSecureCLIStore) SetUserCredentialsTyped(context.Context, uuid.UUID, string, []byte, *string, *string) error {
return nil
}
func (s *gatewayOperatorSecureCLIStore) DeleteUserCredentials(context.Context, uuid.UUID, string) error {
return nil
}
func (s *gatewayOperatorSecureCLIStore) ListUserCredentials(context.Context, uuid.UUID) ([]store.SecureCLIUserCredential, error) {
return nil, nil
}
type gatewayOperatorGrantStore struct {
grants map[uuid.UUID]*store.SecureCLIAgentGrant
env map[uuid.UUID][]byte
}
func (s *gatewayOperatorGrantStore) BinaryExists(context.Context, uuid.UUID) (bool, error) {
return true, nil
}
func (s *gatewayOperatorGrantStore) AgentExists(context.Context, uuid.UUID) (bool, error) {
return true, nil
}
func (s *gatewayOperatorGrantStore) Create(_ context.Context, g *store.SecureCLIAgentGrant) error {
if s.grants == nil {
s.grants = map[uuid.UUID]*store.SecureCLIAgentGrant{}
}
if g.ID == uuid.Nil {
g.ID = store.GenNewID()
}
cp := *g
s.grants[g.ID] = &cp
return nil
}
func (s *gatewayOperatorGrantStore) Get(_ context.Context, id uuid.UUID) (*store.SecureCLIAgentGrant, error) {
if g := s.grants[id]; g != nil {
cp := *g
return &cp, nil
}
return nil, sql.ErrNoRows
}
func (s *gatewayOperatorGrantStore) Update(_ context.Context, id uuid.UUID, updates map[string]any) error {
g := s.grants[id]
if g == nil {
return sql.ErrNoRows
}
if enabled, ok := updates["enabled"].(bool); ok {
g.Enabled = enabled
}
return nil
}
func (s *gatewayOperatorGrantStore) Delete(context.Context, uuid.UUID) error { return nil }
func (s *gatewayOperatorGrantStore) ListByBinary(_ context.Context, binaryID uuid.UUID) ([]store.SecureCLIAgentGrant, error) {
out := []store.SecureCLIAgentGrant{}
for _, g := range s.grants {
if g.BinaryID == binaryID {
out = append(out, *g)
}
}
return out, nil
}
func (s *gatewayOperatorGrantStore) ListByAgent(_ context.Context, agentID uuid.UUID) ([]store.SecureCLIAgentGrant, error) {
out := []store.SecureCLIAgentGrant{}
for _, g := range s.grants {
if g.AgentID == agentID {
out = append(out, *g)
}
}
return out, nil
}
func (s *gatewayOperatorGrantStore) UpdateGrantEnv(_ context.Context, grantID uuid.UUID, plaintextEnv []byte) error {
if s.env == nil {
s.env = map[uuid.UUID][]byte{}
}
if s.grants[grantID] == nil {
return sql.ErrNoRows
}
cp := append([]byte(nil), plaintextEnv...)
s.env[grantID] = cp
s.grants[grantID].EncryptedEnv = cp
return nil
}
type gatewayOperatorAgentCredentialStore struct {
env map[uuid.UUID][]byte
createdBy map[uuid.UUID]string
}
func (s *gatewayOperatorAgentCredentialStore) BinaryExists(context.Context, uuid.UUID) (bool, error) {
return true, nil
}
func (s *gatewayOperatorAgentCredentialStore) AgentExists(context.Context, uuid.UUID) (bool, error) {
return true, nil
}
func (s *gatewayOperatorAgentCredentialStore) GetAgentCredentials(_ context.Context, _ uuid.UUID, agentID uuid.UUID) (*store.SecureCLIAgentCredential, error) {
if s.env == nil || len(s.env[agentID]) == 0 {
return nil, nil
}
return &store.SecureCLIAgentCredential{
ID: store.GenNewID(),
AgentID: agentID,
EncryptedEnv: append([]byte(nil), s.env[agentID]...),
CreatedBy: s.createdBy[agentID],
}, nil
}
func (s *gatewayOperatorAgentCredentialStore) SetAgentCredentials(_ context.Context, _ uuid.UUID, agentID uuid.UUID, encryptedEnv []byte, createdBy string) error {
if s.env == nil {
s.env = map[uuid.UUID][]byte{}
}
if s.createdBy == nil {
s.createdBy = map[uuid.UUID]string{}
}
s.env[agentID] = append([]byte(nil), encryptedEnv...)
s.createdBy[agentID] = createdBy
return nil
}
func (s *gatewayOperatorAgentCredentialStore) SetAgentCredentialsTyped(ctx context.Context, binaryID uuid.UUID, agentID uuid.UUID, encryptedEnv []byte, _ *string, _ *string, createdBy string) error {
return s.SetAgentCredentials(ctx, binaryID, agentID, encryptedEnv, createdBy)
}
func (s *gatewayOperatorAgentCredentialStore) DeleteAgentCredentials(context.Context, uuid.UUID, uuid.UUID) error {
return nil
}
func (s *gatewayOperatorAgentCredentialStore) ListAgentCredentials(context.Context, uuid.UUID) ([]store.SecureCLIAgentCredential, error) {
return nil, nil
}
func gatewayOperatorContext() context.Context {
ctx := context.Background()
ctx = store.WithTenantID(ctx, uuid.MustParse("0193a5b0-7000-7000-8000-000000000002"))
ctx = store.WithUserID(ctx, "system")
ctx = store.WithRole(ctx, store.RoleOwner)
return ctx
}
type gatewayOperatorAgentStore struct {
store.AgentStore
agents []store.AgentData
files map[uuid.UUID]map[string]string
}
func (s *gatewayOperatorAgentStore) Create(_ context.Context, agent *store.AgentData) error {
if agent.ID == uuid.Nil {
agent.ID = store.GenNewID()
}
if agent.CreatedAt.IsZero() {
agent.CreatedAt = time.Now().UTC()
}
agent.UpdatedAt = agent.CreatedAt
cp := *agent
s.agents = append(s.agents, cp)
return nil
}
func (s *gatewayOperatorAgentStore) GetByKey(_ context.Context, agentKey string) (*store.AgentData, error) {
for i := range s.agents {
if s.agents[i].AgentKey == agentKey {
cp := s.agents[i]
return &cp, nil
}
}
return nil, sql.ErrNoRows
}
func (s *gatewayOperatorAgentStore) List(context.Context, string) ([]store.AgentData, error) {
out := make([]store.AgentData, len(s.agents))
copy(out, s.agents)
return out, nil
}
func (s *gatewayOperatorAgentStore) GetAgentContextFiles(context.Context, uuid.UUID) ([]store.AgentContextFileData, error) {
return nil, nil
}
func (s *gatewayOperatorAgentStore) SetAgentContextFile(_ context.Context, agentID uuid.UUID, fileName, content string) error {
if s.files == nil {
s.files = map[uuid.UUID]map[string]string{}
}
if s.files[agentID] == nil {
s.files[agentID] = map[string]string{}
}
s.files[agentID][fileName] = content
return nil
}
func TestGatewayOperatorBootstrapCreatesBinaryGrantAndSensitiveTokenEnv(t *testing.T) {
setupTestToken(t, "test-gateway-token")
secureCLI := &gatewayOperatorSecureCLIStore{}
grants := &gatewayOperatorGrantStore{}
agentCreds := &gatewayOperatorAgentCredentialStore{}
agentID := uuid.New()
handler := &AgentsHandler{}
handler.SetGatewayOperatorBootstrap(secureCLI, grants, agentCreds, "http://127.0.0.1:18790")
handler.findGatewayOperatorBinary = func() (string, error) {
return "/usr/local/bin/goclaw", nil
}
result, err := handler.bootstrapGatewayOperatorAccess(gatewayOperatorContext(), agentID)
if err != nil {
t.Fatalf("bootstrap returned error: %v", err)
}
if result.BinaryID == uuid.Nil || result.GrantID == uuid.Nil {
t.Fatalf("bootstrap result missing IDs: %#v", result)
}
if secureCLI.created == nil {
t.Fatal("expected goclaw secure CLI binary to be registered")
}
if secureCLI.created.BinaryName != "goclaw" {
t.Fatalf("binary name=%q, want goclaw", secureCLI.created.BinaryName)
}
if secureCLI.created.IsGlobal {
t.Fatal("gateway operator binary must be non-global")
}
if secureCLI.created.BinaryPath == nil || *secureCLI.created.BinaryPath != "/usr/local/bin/goclaw" {
t.Fatalf("binary path not recorded: %#v", secureCLI.created.BinaryPath)
}
if !strings.Contains(string(secureCLI.created.DenyArgs), "auth") {
t.Fatalf("expected lifecycle/auth deny patterns, got %s", string(secureCLI.created.DenyArgs))
}
if len(grants.env) != 0 {
t.Fatalf("gateway token must not be stored in revealable grant env: %#v", grants.env)
}
envJSON := agentCreds.env[agentID]
if len(envJSON) == 0 {
t.Fatal("expected non-revealable per-agent credential env")
}
entries, err := store.ParseSecureCLIEnv(envJSON)
if err != nil {
t.Fatalf("parse agent credential env: %v", err)
}
tokenEntry := entries["GOCLAW_GATEWAY_TOKEN"]
if tokenEntry.Kind != store.SecureCLIEnvKindSensitive || tokenEntry.Value != "test-gateway-token" {
t.Fatalf("token entry not stored as sensitive override: %#v", tokenEntry)
}
if entries["GOCLAW_SERVER"].Value != "http://127.0.0.1:18790" {
t.Fatalf("GOCLAW_SERVER not injected: %#v", entries["GOCLAW_SERVER"])
}
safeEnv := agentCredentialResponse(store.SecureCLIAgentCredential{EncryptedEnv: envJSON}).Env
if safeEnv["GOCLAW_GATEWAY_TOKEN"].Value != nil || !safeEnv["GOCLAW_GATEWAY_TOKEN"].Masked {
encoded, _ := json.Marshal(safeEnv)
t.Fatalf("gateway token not masked in agent credential response: %s", encoded)
}
}
func TestGatewayOperatorBootstrapReusesExistingGrant(t *testing.T) {
setupTestToken(t, "rotated-test-token")
binaryID := uuid.New()
grantID := uuid.New()
agentID := uuid.New()
secureCLI := &gatewayOperatorSecureCLIStore{
binaries: []store.SecureCLIBinary{{
BaseModel: store.BaseModel{ID: binaryID},
BinaryName: "goclaw",
Enabled: true,
IsGlobal: false,
}},
}
grants := &gatewayOperatorGrantStore{
grants: map[uuid.UUID]*store.SecureCLIAgentGrant{
grantID: {
BaseModel: store.BaseModel{ID: grantID},
BinaryID: binaryID,
AgentID: agentID,
Enabled: false,
},
},
}
agentCreds := &gatewayOperatorAgentCredentialStore{}
handler := &AgentsHandler{}
handler.SetGatewayOperatorBootstrap(secureCLI, grants, agentCreds, "http://127.0.0.1:18790")
handler.findGatewayOperatorBinary = func() (string, error) {
return "/usr/local/bin/goclaw", nil
}
result, err := handler.bootstrapGatewayOperatorAccess(gatewayOperatorContext(), agentID)
if err != nil {
t.Fatalf("bootstrap returned error: %v", err)
}
if result.GrantID != grantID {
t.Fatalf("expected existing grant %s, got %s", grantID, result.GrantID)
}
if !grants.grants[grantID].Enabled {
t.Fatal("existing disabled grant should be re-enabled")
}
if secureCLI.created != nil {
t.Fatal("existing binary should be reused, not recreated")
}
if len(agentCreds.env[agentID]) == 0 {
t.Fatal("expected agent credential env to be refreshed for existing grant")
}
}
func TestGatewayOperatorBootstrapConvertsExistingGlobalBinaryToGrantScoped(t *testing.T) {
setupTestToken(t, "test-gateway-token")
binaryID := uuid.New()
agentID := uuid.New()
secureCLI := &gatewayOperatorSecureCLIStore{
binaries: []store.SecureCLIBinary{{
BaseModel: store.BaseModel{ID: binaryID},
BinaryName: "goclaw",
Enabled: true,
IsGlobal: true,
}},
}
grants := &gatewayOperatorGrantStore{}
agentCreds := &gatewayOperatorAgentCredentialStore{}
handler := &AgentsHandler{}
handler.SetGatewayOperatorBootstrap(secureCLI, grants, agentCreds, "127.0.0.1:19999")
handler.findGatewayOperatorBinary = func() (string, error) {
return "/opt/goclaw/current/goclaw", nil
}
result, err := handler.bootstrapGatewayOperatorAccess(gatewayOperatorContext(), agentID)
if err != nil {
t.Fatalf("bootstrap returned error: %v", err)
}
if result.BinaryID != binaryID {
t.Fatalf("expected existing binary %s, got %s", binaryID, result.BinaryID)
}
if secureCLI.created != nil {
t.Fatal("existing binary should be policy-updated, not duplicated")
}
updates := secureCLI.updated[binaryID]
if updates == nil {
t.Fatal("expected existing global binary to be updated")
}
if updates["is_global"] != false || updates["enabled"] != true {
t.Fatalf("expected explicit non-global enabled policy, got %#v", updates)
}
if got := secureCLI.binaries[0].BinaryPath; got == nil || *got != "/opt/goclaw/current/goclaw" {
t.Fatalf("binary path not updated: %#v", got)
}
if len(grants.grants) != 1 {
t.Fatalf("expected one explicit agent grant, got %d", len(grants.grants))
}
entries, err := store.ParseSecureCLIEnv(agentCreds.env[agentID])
if err != nil {
t.Fatalf("parse agent credential env: %v", err)
}
if entries["GOCLAW_SERVER"].Value != "http://127.0.0.1:19999" {
t.Fatalf("GOCLAW_SERVER not normalized: %#v", entries["GOCLAW_SERVER"])
}
}
func TestGatewayOperatorBootstrapRewritesUnsafeExistingNonGlobalBinary(t *testing.T) {
setupTestToken(t, "test-gateway-token")
binaryID := uuid.New()
agentID := uuid.New()
unsafePath := "/tmp/wrapper-goclaw"
adapterName := "git"
secureCLI := &gatewayOperatorSecureCLIStore{
binaries: []store.SecureCLIBinary{{
BaseModel: store.BaseModel{ID: binaryID},
BinaryName: "goclaw",
BinaryPath: &unsafePath,
Enabled: true,
IsGlobal: false,
DenyArgs: json.RawMessage(`[]`),
DenyVerbose: json.RawMessage(`[]`),
TimeoutSeconds: 300,
AdapterName: &adapterName,
}},
}
grants := &gatewayOperatorGrantStore{}
agentCreds := &gatewayOperatorAgentCredentialStore{}
handler := &AgentsHandler{}
handler.SetGatewayOperatorBootstrap(secureCLI, grants, agentCreds, "http://127.0.0.1:18790")
handler.findGatewayOperatorBinary = func() (string, error) {
return "/opt/goclaw/current/goclaw", nil
}
result, err := handler.bootstrapGatewayOperatorAccess(gatewayOperatorContext(), agentID)
if err != nil {
t.Fatalf("bootstrap returned error: %v", err)
}
if result.BinaryID != binaryID {
t.Fatalf("expected existing binary %s, got %s", binaryID, result.BinaryID)
}
updates := secureCLI.updated[binaryID]
if updates == nil {
t.Fatal("expected unsafe existing binary policy to be rewritten before grant")
}
if got := secureCLI.binaries[0].BinaryPath; got == nil || *got != "/opt/goclaw/current/goclaw" {
t.Fatalf("safe binary path not enforced: %#v", got)
}
if !strings.Contains(string(secureCLI.binaries[0].DenyArgs), "migrate") {
t.Fatalf("safe deny policy not enforced: %s", string(secureCLI.binaries[0].DenyArgs))
}
if secureCLI.binaries[0].AdapterName != nil {
t.Fatalf("gateway operator binary must use passthrough adapter, got %q", *secureCLI.binaries[0].AdapterName)
}
if len(grants.grants) != 1 || len(agentCreds.env[agentID]) == 0 {
t.Fatal("expected grant and non-revealable credential after safe policy rewrite")
}
}
func TestAgentsCreateAddsGatewayOperatorBootstrapMetadataWhenRequested(t *testing.T) {
setupTestToken(t, "test-gateway-token")
agents := &gatewayOperatorAgentStore{}
secureCLI := &gatewayOperatorSecureCLIStore{}
grants := &gatewayOperatorGrantStore{}
agentCreds := &gatewayOperatorAgentCredentialStore{}
handler := &AgentsHandler{
agents: agents,
defaultWorkspace: "/tmp/workspace",
}
handler.SetGatewayOperatorBootstrap(secureCLI, grants, agentCreds, "http://127.0.0.1:18790")
handler.findGatewayOperatorBinary = func() (string, error) {
return "/usr/local/bin/goclaw", nil
}
body := []byte(`{
"agent_key":"assistant",
"display_name":"Assistant",
"provider":"anthropic",
"model":"claude",
"grant_gateway_operator_access":true
}`)
req := httptest.NewRequest(http.MethodPost, "/v1/agents", bytes.NewReader(body))
req = req.WithContext(gatewayOperatorContext())
rr := httptest.NewRecorder()
handler.handleCreate(rr, req)
if rr.Code != http.StatusCreated {
t.Fatalf("status=%d body=%s", rr.Code, rr.Body.String())
}
var response struct {
ID uuid.UUID `json:"id"`
AgentKey string `json:"agent_key"`
GatewayOperatorBootstrap *gatewayOperatorBootstrapResult `json:"gateway_operator_bootstrap"`
}
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Fatalf("decode response: %v body=%s", err, rr.Body.String())
}
if response.ID == uuid.Nil || response.AgentKey != "assistant" {
t.Fatalf("unexpected agent response: %#v", response)
}
if response.GatewayOperatorBootstrap == nil || response.GatewayOperatorBootstrap.Status != "granted" {
t.Fatalf("expected granted bootstrap metadata, got %#v body=%s", response.GatewayOperatorBootstrap, rr.Body.String())
}
if len(grants.grants) != 1 {
t.Fatalf("expected one grant, got %d", len(grants.grants))
}
if len(agentCreds.env[response.ID]) == 0 {
t.Fatal("expected per-agent gateway credentials to be stored")
}
if strings.Contains(rr.Body.String(), "test-gateway-token") {
t.Fatalf("gateway token leaked in create response: %s", rr.Body.String())
}
}
func TestGatewayOperatorBootstrapFailsClosedWithoutToken(t *testing.T) {
setupTestToken(t, "")
secureCLI := &gatewayOperatorSecureCLIStore{}
grants := &gatewayOperatorGrantStore{}
agentCreds := &gatewayOperatorAgentCredentialStore{}
handler := &AgentsHandler{}
handler.SetGatewayOperatorBootstrap(secureCLI, grants, agentCreds, "http://127.0.0.1:18790")
handler.findGatewayOperatorBinary = func() (string, error) {
return "/usr/local/bin/goclaw", nil
}
_, err := handler.bootstrapGatewayOperatorAccess(gatewayOperatorContext(), uuid.New())
if err == nil {
t.Fatal("expected missing gateway token to fail closed")
}
if secureCLI.created != nil || len(grants.grants) > 0 || len(agentCreds.env) > 0 {
t.Fatal("missing token must not create binary, grant, or agent credential")
}
}
func TestGatewayOperatorBootstrapReportsBinaryDiscoveryFailure(t *testing.T) {
setupTestToken(t, "test-gateway-token")
handler := &AgentsHandler{}
handler.SetGatewayOperatorBootstrap(&gatewayOperatorSecureCLIStore{}, &gatewayOperatorGrantStore{}, &gatewayOperatorAgentCredentialStore{}, "http://127.0.0.1:18790")
handler.findGatewayOperatorBinary = func() (string, error) {
return "", errors.New("not found")
}
_, err := handler.bootstrapGatewayOperatorAccess(gatewayOperatorContext(), uuid.New())
if !errors.Is(err, errGatewayOperatorBinaryMissing) {
t.Fatalf("expected binary discovery warning, got %v", err)
}
}
func TestGatewayOperatorFirstAgentGateUsesDeterministicEarliestRow(t *testing.T) {
firstID := uuid.MustParse("0193a5b0-7000-7000-8000-000000000001")
secondID := uuid.MustParse("0193a5b0-7000-7000-8000-000000000002")
createdAt := time.Date(2026, 6, 12, 10, 0, 0, 0, time.UTC)
got := deterministicFirstAgentID([]store.AgentData{
{BaseModel: store.BaseModel{ID: secondID, CreatedAt: createdAt.Add(time.Second)}},
{BaseModel: store.BaseModel{ID: firstID, CreatedAt: createdAt}},
})
if got != firstID {
t.Fatalf("deterministic first id=%s, want %s", got, firstID)
}
tieWinner := deterministicFirstAgentID([]store.AgentData{
{BaseModel: store.BaseModel{ID: secondID, CreatedAt: createdAt}},
{BaseModel: store.BaseModel{ID: firstID, CreatedAt: createdAt}},
})
if tieWinner != firstID {
t.Fatalf("tie winner id=%s, want lexical uuid %s", tieWinner, firstID)
}
}