- 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>
99 lines
3.2 KiB
Go
99 lines
3.2 KiB
Go
// InMemoryRepository(agent)的單元測試(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)
|
||
}
|