test(devices): #27 同机换账号登录注册 E2E(SQLite,服务层)
走 login 完全相同的路径 devices.Service.RegisterIfAbsent{MaxDevices:0}:
同一 device uuid 两账号各得一行、A 重登幂等刷新、共享 uuid 恰 2 行。
补 SQLite(生产同引擎)服务层覆盖,与既有 store 层 TestSQLite_DevicesUserScopedUUID
及 MySQL 集成 TestSameDeviceUUIDTwoAccounts 三层互证 F3 修复。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/wangjia/pangolin/server/internal/devices"
|
||||
)
|
||||
|
||||
// TestSQLite_LoginRegistersSameDeviceForTwoAccounts reproduces the #27 (F3) fix
|
||||
// at the exact code path login drives: recordLogin → devReg.RegisterDevice →
|
||||
// devices.Service.RegisterIfAbsent{MaxDevices:0}. Two accounts on the SAME physical
|
||||
// device (same device uuid) must each get their own devices row.
|
||||
//
|
||||
// Under the old global UNIQUE(uuid) the second account's registration failed
|
||||
// (silently on login, best-effort) → the device row for account B never existed →
|
||||
// ConnectNode later reported DEVICE_NOT_REGISTERED (the 403 deadlock, F3).
|
||||
func TestSQLite_LoginRegistersSameDeviceForTwoAccounts(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
db := openSQLite(t)
|
||||
|
||||
mkUser := func(u, email string) int64 {
|
||||
res, err := db.ExecContext(ctx,
|
||||
`INSERT INTO users (uuid, email, pw_hash, dp_uuid) VALUES (?, ?, 'h', ?)`,
|
||||
u, email, "dp-"+u)
|
||||
if err != nil {
|
||||
t.Fatalf("user %s: %v", u, err)
|
||||
}
|
||||
id, _ := res.LastInsertId()
|
||||
return id
|
||||
}
|
||||
userA := mkUser("u-a", "a@x.c")
|
||||
userB := mkUser("u-b", "b@x.c")
|
||||
|
||||
svc := devices.NewService(devices.NewStore(db), nil)
|
||||
const sharedUUID = "shared-install-uuid"
|
||||
|
||||
// Account A "logs in" on the device.
|
||||
idA, _, apiErr := svc.RegisterIfAbsent(ctx, devices.RegisterInput{
|
||||
UserID: userA, DeviceUUID: sharedUUID, Platform: "macos", MaxDevices: 0,
|
||||
})
|
||||
if apiErr != nil || idA == 0 {
|
||||
t.Fatalf("A register: id=%d err=%v", idA, apiErr)
|
||||
}
|
||||
|
||||
// Account B "logs in" on the SAME physical device (same uuid) — the F3 case.
|
||||
idB, _, apiErr := svc.RegisterIfAbsent(ctx, devices.RegisterInput{
|
||||
UserID: userB, DeviceUUID: sharedUUID, Platform: "macos", MaxDevices: 0,
|
||||
})
|
||||
if apiErr != nil {
|
||||
t.Fatalf("B register on same device uuid must succeed (F3 fix), got %v", apiErr)
|
||||
}
|
||||
if idB == 0 || idB == idA {
|
||||
t.Fatalf("B must get its own device row, idA=%d idB=%d", idA, idB)
|
||||
}
|
||||
|
||||
// A logs in again → idempotent refresh of A's own row (not a new row).
|
||||
idA2, _, apiErr := svc.RegisterIfAbsent(ctx, devices.RegisterInput{
|
||||
UserID: userA, DeviceUUID: sharedUUID, Platform: "macos", MaxDevices: 0,
|
||||
})
|
||||
if apiErr != nil || idA2 != idA {
|
||||
t.Fatalf("A re-register must refresh same row: id=%d (want %d) err=%v", idA2, idA, apiErr)
|
||||
}
|
||||
|
||||
// Both rows coexist for the shared uuid, one per account.
|
||||
var n int
|
||||
if err := db.QueryRowContext(ctx,
|
||||
`SELECT COUNT(*) FROM devices WHERE uuid=?`, sharedUUID).Scan(&n); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 2 {
|
||||
t.Fatalf("shared uuid must have 2 rows (one per account), got %d", n)
|
||||
}
|
||||
t.Logf("OK: device uuid %q shared by account A (row #%d) + account B (row #%d)", sharedUUID, idA, idB)
|
||||
}
|
||||
Reference in New Issue
Block a user