Files
xiaomihome/miot/common_test.go
T
4566704 cdc459b5ff fix(xiaomihome): 模块路径改为 git.zeroonesoft.cn/golib/xiaomihome
- 改动:go.mod module 声明与全部内部导入路径补全为完整域名路径,README 同步
- 原因:裸模块名无法被 go get 解析,发版前必须修正为 GOPRIVATE 可拉取路径
2026-10-05 08:53:23 +08:00

328 lines
9.2 KiB
Go

// Package miot_test provides black-box tests for common.go utility functions.
package miot_test
import (
"encoding/hex"
"os"
"path/filepath"
"testing"
miot "git.zeroonesoft.cn/golib/xiaomihome/miot"
)
// ============================================================================
// CalcGroupID
// ============================================================================
func TestCalcGroupID(t *testing.T) {
uid := "test_user"
homeID := "12345"
id1 := miot.CalcGroupID(uid, homeID)
id2 := miot.CalcGroupID(uid, homeID)
// Same inputs → same output
if id1 != id2 {
t.Errorf("CalcGroupID should be deterministic: %q != %q", id1, id2)
}
// Length should be 16 (hex encoding of first 8 bytes of SHA1)
if len(id1) != 16 {
t.Errorf("CalcGroupID length = %d, want 16", len(id1))
}
// Should be valid hex
if _, err := hex.DecodeString(id1); err != nil {
t.Errorf("CalcGroupID should produce valid hex: %v", err)
}
// Different inputs → different output
id3 := miot.CalcGroupID(uid, "99999")
if id1 == id3 {
t.Error("CalcGroupID should produce different results for different inputs")
}
}
func TestCalcGroupID_EmptyInputs(t *testing.T) {
// Empty inputs should still work without panic
id := miot.CalcGroupID("", "")
if len(id) != 16 {
t.Errorf("CalcGroupID with empty inputs length = %d, want 16", len(id))
}
}
// ============================================================================
// GenAbsolutePath
// ============================================================================
func TestGenAbsolutePath(t *testing.T) {
// Save and restore RootPath
origRoot := miot.RootPath
miot.RootPath = "/test/root"
defer func() { miot.RootPath = origRoot }()
abs := miot.GenAbsolutePath("subdir/file.txt")
expected := filepath.Join("/test/root", "subdir/file.txt")
if abs != expected {
t.Errorf("GenAbsolutePath = %q, want %q", abs, expected)
}
}
func TestGenAbsolutePath_Empty(t *testing.T) {
origRoot := miot.RootPath
miot.RootPath = "/test/root"
defer func() { miot.RootPath = origRoot }()
abs := miot.GenAbsolutePath("")
// Should return RootPath itself when empty string is given
if abs != "/test/root" && abs != "/test/root"+string(filepath.Separator) {
// filepath.Join behavior varies, just check it's non-empty
if abs == "" {
t.Error("GenAbsolutePath with empty string should not be empty")
}
}
}
// ============================================================================
// LoadJSONFile
// ============================================================================
func TestLoadJSONFile_Valid(t *testing.T) {
// Create a temporary JSON file
tmpDir := t.TempDir()
jsonPath := filepath.Join(tmpDir, "test.json")
content := `{"key1": "value1", "key2": 42, "key3": true}`
if err := os.WriteFile(jsonPath, []byte(content), 0644); err != nil {
t.Fatal(err)
}
data, err := miot.LoadJSONFile(jsonPath)
if err != nil {
t.Fatalf("LoadJSONFile should succeed: %v", err)
}
if data["key1"] != "value1" {
t.Errorf("key1 = %v, want 'value1'", data["key1"])
}
if data["key2"].(float64) != 42 {
t.Errorf("key2 = %v, want 42", data["key2"])
}
if data["key3"] != true {
t.Errorf("key3 = %v, want true", data["key3"])
}
}
func TestLoadJSONFile_NotExist(t *testing.T) {
_, err := miot.LoadJSONFile("/nonexistent/path/file.json")
if err == nil {
t.Error("LoadJSONFile should return error for nonexistent file")
}
}
func TestLoadJSONFile_InvalidJSON(t *testing.T) {
tmpDir := t.TempDir()
jsonPath := filepath.Join(tmpDir, "invalid.json")
content := `{not valid json`
if err := os.WriteFile(jsonPath, []byte(content), 0644); err != nil {
t.Fatal(err)
}
_, err := miot.LoadJSONFile(jsonPath)
if err == nil {
t.Error("LoadJSONFile should return error for invalid JSON")
}
}
func TestLoadJSONFile_EmptyFile(t *testing.T) {
tmpDir := t.TempDir()
jsonPath := filepath.Join(tmpDir, "empty.json")
if err := os.WriteFile(jsonPath, []byte(""), 0644); err != nil {
t.Fatal(err)
}
// Empty file is not valid JSON object
_, err := miot.LoadJSONFile(jsonPath)
if err == nil {
t.Error("LoadJSONFile should return error for empty file")
}
}
// ============================================================================
// LoadYAMLFile
// ============================================================================
func TestLoadYAMLFile_Valid(t *testing.T) {
tmpDir := t.TempDir()
yamlPath := filepath.Join(tmpDir, "test.yaml")
content := "key1: value1\nkey2: 42\nkey3: true\n"
if err := os.WriteFile(yamlPath, []byte(content), 0644); err != nil {
t.Fatal(err)
}
data, err := miot.LoadYAMLFile(yamlPath)
if err != nil {
t.Fatalf("LoadYAMLFile should succeed: %v", err)
}
if data["key1"] != "value1" {
t.Errorf("key1 = %v, want 'value1'", data["key1"])
}
}
func TestLoadYAMLFile_NotExist(t *testing.T) {
_, err := miot.LoadYAMLFile("/nonexistent/path/file.yaml")
if err == nil {
t.Error("LoadYAMLFile should return error for nonexistent file")
}
}
func TestLoadYAMLFile_InvalidYAML(t *testing.T) {
tmpDir := t.TempDir()
yamlPath := filepath.Join(tmpDir, "invalid.yaml")
// Invalid YAML that will cause parsing error
content := "\tinvalid\tyaml\tbad"
if err := os.WriteFile(yamlPath, []byte(content), 0644); err != nil {
t.Fatal(err)
}
// YAML is lenient; this might parse as a string. Just ensure no panic.
_, _ = miot.LoadYAMLFile(yamlPath)
}
// ============================================================================
// RandomizeInt
// ============================================================================
func TestRandomizeInt(t *testing.T) {
value := 100
ratio := 0.3
minVal := int(float64(value) * (1 - ratio))
maxVal := int(float64(value) * (1 + ratio))
// Run multiple times to cover the range
for i := 0; i < 100; i++ {
result := miot.RandomizeInt(value, ratio)
if result < minVal || result > maxVal {
t.Errorf("RandomizeInt(%d, %f) = %d, expected in [%d, %d]", value, ratio, result, minVal, maxVal)
}
}
}
func TestRandomizeInt_ZeroRatio(t *testing.T) {
value := 50
result := miot.RandomizeInt(value, 0)
if result != value {
t.Errorf("RandomizeInt(%d, 0) = %d, want %d", value, result, value)
}
}
// ============================================================================
// RandomizeFloat
// ============================================================================
func TestRandomizeFloat(t *testing.T) {
value := 10.0
ratio := 0.2
minVal := value * (1 - ratio)
maxVal := value * (1 + ratio)
for i := 0; i < 100; i++ {
result := miot.RandomizeFloat(value, ratio)
if result < minVal || result > maxVal {
t.Errorf("RandomizeFloat(%f, %f) = %f, expected in [%f, %f]", value, ratio, result, minVal, maxVal)
}
}
}
func TestRandomizeFloat_ZeroRatio(t *testing.T) {
value := 3.14
result := miot.RandomizeFloat(value, 0)
if result != value {
t.Errorf("RandomizeFloat(%f, 0) = %f, want %f", value, result, value)
}
}
// ============================================================================
// SlugifyName
// ============================================================================
func TestSlugifyName(t *testing.T) {
tests := []struct {
input string
want string
}{
{"Hello World", "hello_world"},
{"Xiaomi Home", "xiaomi_home"},
{"UPPERCASE", "uppercase"},
{"a b c", "a_b_c"},
{"already_slug", "already_slug"},
{"", ""},
{" spaces ", "spaces"},
{"special!@#chars", "special_chars"},
{"混合中文English", "english"}, // non-alpha chars become underscores, trim
{"dash-separated", "dash_separated"}, // dash is non-alphanumeric
{"123numbers456", "123numbers456"},
{"__leading_trailing__", "leading_trailing"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
if got := miot.SlugifyName(tt.input); got != tt.want {
t.Errorf("SlugifyName(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}
// ============================================================================
// SlugifyDID
// ============================================================================
func TestSlugifyDID(t *testing.T) {
server := "cn"
did := "device_123"
result := miot.SlugifyDID(server, did)
// Should equal SlugifyName("cn_device_123")
expected := miot.SlugifyName(server + "_" + did)
if result != expected {
t.Errorf("SlugifyDID(%q, %q) = %q, want %q", server, did, result, expected)
}
}
func TestSlugifyDID_NonASCIIDID(t *testing.T) {
// Even with non-ASCII DID, should not panic
result := miot.SlugifyDID("cn", "did.test")
if result == "" {
t.Error("SlugifyDID should not return empty string for valid inputs")
}
}
// ============================================================================
// RootPath
// ============================================================================
func TestRootPath_Initialized(t *testing.T) {
// RootPath should be set during init()
if miot.RootPath == "" {
t.Error("RootPath should not be empty after init()")
}
}
// ============================================================================
// Benchmark
// ============================================================================
func BenchmarkCalcGroupID(b *testing.B) {
for i := 0; i < b.N; i++ {
miot.CalcGroupID("test_uid", "test_home")
}
}
func BenchmarkSlugifyName(b *testing.B) {
for i := 0; i < b.N; i++ {
miot.SlugifyName("Xiaomi Smart Home Device")
}
}