package api import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "visiona-backend/internal/auth" "visiona-backend/internal/device" ) // TestInMemoryDeviceUnpairer_Cascade 驗證 in-memory 模式刪 device 時 cascade 撤銷其 token // (塊 5.2 約束:DB 未啟用時 cascade 在 in-memory 也成立)。 func TestInMemoryDeviceUnpairer_Cascade(t *testing.T) { ctx := context.Background() devRepo := device.NewInMemoryRepository() pairing := auth.NewInMemoryPairingStore() sessions := auth.NewInMemorySessionTokenStore() const userID = "user-1" const deviceID = "dev-1" // 建一個 device require.NoError(t, devRepo.Save(ctx, &device.Device{ ID: deviceID, OwnerUserID: userID, Name: "d", SerialNumber: "SN1", })) // 建並綁定一個 pairing token 到該 device(MarkUsed 綁 device) pPlain, _, err := pairing.Create(ctx, userID, 15*time.Minute) require.NoError(t, err) require.NoError(t, pairing.MarkUsed(ctx, pPlain, deviceID)) // 建一個 session token 綁該 device sPlain, _, err := sessions.Create(ctx, userID, deviceID, "", 90*24*time.Hour) require.NoError(t, err) // 另一個 device 的 token(不應被撤) const otherDevice = "dev-2" require.NoError(t, devRepo.Save(ctx, &device.Device{ ID: otherDevice, OwnerUserID: userID, Name: "d2", SerialNumber: "SN2", })) oPlain, _, err := sessions.Create(ctx, userID, otherDevice, "", 90*24*time.Hour) require.NoError(t, err) unpairer := NewInMemoryDeviceUnpairer(devRepo, pairing, sessions) res, err := unpairer.Unpair(ctx, deviceID) require.NoError(t, err) assert.Equal(t, 1, res.PairingRevoked) assert.Equal(t, 1, res.SessionRevoked) // device 已軟刪 _, err = devRepo.Get(ctx, deviceID) assert.ErrorIs(t, err, device.ErrNotFound) // 該 device 的 session token 已撤 _, err = sessions.Get(ctx, sPlain) assert.ErrorIs(t, err, auth.ErrTokenRevoked) // 該 device 的 pairing token 已撤 _, err = pairing.Validate(ctx, pPlain) assert.ErrorIs(t, err, auth.ErrTokenRevoked) // 另一個 device 的 token 不受影響 _, err = sessions.Get(ctx, oPlain) assert.NoError(t, err) } // TestInMemoryDeviceUnpairer_NotFound 驗證刪不存在 device → device.ErrNotFound、不撤任何 token。 func TestInMemoryDeviceUnpairer_NotFound(t *testing.T) { ctx := context.Background() devRepo := device.NewInMemoryRepository() pairing := auth.NewInMemoryPairingStore() sessions := auth.NewInMemorySessionTokenStore() unpairer := NewInMemoryDeviceUnpairer(devRepo, pairing, sessions) _, err := unpairer.Unpair(ctx, "does-not-exist") assert.ErrorIs(t, err, device.ErrNotFound) }