visionA/visionA-backend/internal/agent/inmemory_repository_test.go
jim800121chen 59c57fa481 feat(device): WP-B repository 接 agents 模型 + exchange 重塑(A' 走向第二階段 Go 層)
- Device struct 加 4 欄(agent_id/agent_local_device_id/registered_at/
  is_representative)+ deviceColumns 13→17 + scanDevice/SaveTx 讀寫新欄
- 新增 internal/agent package(domain + interface + in-memory + PG repo):
  GetOrCreateAgentTx/GetByOwnerTx,advisory lock 序列化同 owner get-or-create
- exchange 重塑:建/復用 agent → representative device(綁 session_tokens、
  serial=NULL)→ loop 建 N 顆真 USB device(R1 完整 N 顆非只第一顆)
- List filter is_representative=false + DeviceListItem 回傳 agent_id/registered_at
- 併入 WP-0/0005 follow-up Minor:Mi#2 lost-update 收斂(tx 內查詢+局部更新)
  / Mi#3 過時註解 / Mi#4 空 serial 回 ErrNotFound / Mi#5 serial 白名單
  ^0x[0-9A-Fa-f]{8}$ + 去重 / S-1 firmware forward-compat / S-2 device Name 衍生

守 ADR-018 A'(一 owner N agents、session_tokens FK 物理不動、不加 owner
unique 為多機器留路)。Reviewer 通過(0C/0M)。5 套件 dbtest 130 全綠
(db 19/device 37/agent 13/api 172/cmd 60)、build/vet/test 綠。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 11:40:17 +08:00

99 lines
3.2 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.

// InMemoryRepositoryagent的單元測試WP-B B2
//
// 無 build tag預設 `go test ./...` 即涵蓋(不需 Docker。PG 側對齊測試見
// postgres_repository_db_test.go-tags=dbtest
package agent
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestInMemory_GetOrCreate_CreatesWhenAbsent(t *testing.T) {
ctx := context.Background()
r := NewInMemoryRepository()
owner := "owner-1"
paired := time.Now().UTC()
a, err := r.GetOrCreateAgentTx(ctx, nil, owner, "", "", "", paired)
require.NoError(t, err)
require.NotNil(t, a)
assert.NotEmpty(t, a.ID)
assert.Equal(t, owner, a.OwnerUserID)
assert.Equal(t, "local-agent", a.Name, "name 空時應用預設 local-agent")
require.NotNil(t, a.LastPairedAt)
assert.True(t, paired.Equal(*a.LastPairedAt))
}
func TestInMemory_GetOrCreate_ReusesExisting(t *testing.T) {
ctx := context.Background()
r := NewInMemoryRepository()
owner := "owner-2"
first, err := r.GetOrCreateAgentTx(ctx, nil, owner, "local-agent", "darwin", "1.0", time.Now().UTC())
require.NoError(t, err)
time.Sleep(2 * time.Millisecond)
later := time.Now().UTC()
second, err := r.GetOrCreateAgentTx(ctx, nil, owner, "", "", "", later)
require.NoError(t, err)
assert.Equal(t, first.ID, second.ID, "同 owner 應復用同一 agent")
assert.Equal(t, "darwin", second.Platform, "空上報值不應覆寫既有 platform")
assert.Equal(t, "1.0", second.AgentVersion, "空上報值不應覆寫既有 agent_version")
require.NotNil(t, second.LastPairedAt)
assert.True(t, later.Equal(*second.LastPairedAt), "last_paired_at 應更新")
}
func TestInMemory_GetOrCreate_UpdatesNonEmptyFieldsOnReuse(t *testing.T) {
ctx := context.Background()
r := NewInMemoryRepository()
owner := "owner-3"
_, err := r.GetOrCreateAgentTx(ctx, nil, owner, "local-agent", "darwin", "1.0", time.Now().UTC())
require.NoError(t, err)
updated, err := r.GetOrCreateAgentTx(ctx, nil, owner, "renamed", "linux", "2.0", time.Now().UTC())
require.NoError(t, err)
assert.Equal(t, "renamed", updated.Name)
assert.Equal(t, "linux", updated.Platform)
assert.Equal(t, "2.0", updated.AgentVersion)
}
func TestInMemory_GetOrCreate_DistinctOwners(t *testing.T) {
ctx := context.Background()
r := NewInMemoryRepository()
a1, err := r.GetOrCreateAgentTx(ctx, nil, "owner-A", "", "", "", time.Now().UTC())
require.NoError(t, err)
a2, err := r.GetOrCreateAgentTx(ctx, nil, "owner-B", "", "", "", time.Now().UTC())
require.NoError(t, err)
assert.NotEqual(t, a1.ID, a2.ID, "不同 owner 應各自建 agent")
}
func TestInMemory_GetOrCreate_RequiresOwner(t *testing.T) {
r := NewInMemoryRepository()
_, err := r.GetOrCreateAgentTx(context.Background(), nil, "", "", "", "", time.Now().UTC())
assert.Error(t, err)
}
func TestInMemory_GetByOwner(t *testing.T) {
ctx := context.Background()
r := NewInMemoryRepository()
owner := "owner-4"
_, err := r.GetByOwnerTx(ctx, nil, owner)
assert.ErrorIs(t, err, ErrNotFound, "無 agent 時應回 ErrNotFound")
created, err := r.GetOrCreateAgentTx(ctx, nil, owner, "", "", "", time.Now().UTC())
require.NoError(t, err)
got, err := r.GetByOwnerTx(ctx, nil, owner)
require.NoError(t, err)
assert.Equal(t, created.ID, got.ID)
}