feat: 初始化小米 IoT (MIoT) 智能家居 Go 库
- 实现 MIoT 客户端核心功能(MQTT 连接、设备管理、属性读写) - 支持云端 API 调用与局域网设备发现(mDNS) - 集成国际化(i18n)多语言支持 - 添加 MIoT 设备规约解析器(spec_parser) - 包含单元测试与使用示例
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
// Package miot_test provides black-box tests for const.go constants and enums.
|
||||
package miot_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
miot "miot"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// CtrlMode.String()
|
||||
// ============================================================================
|
||||
|
||||
func TestCtrlMode_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
mode miot.CtrlMode
|
||||
want string
|
||||
}{
|
||||
{miot.CtrlModeCloud, "cloud"},
|
||||
{miot.CtrlModeGateway, "gateway"},
|
||||
{miot.CtrlModeLAN, "lan"},
|
||||
{miot.CtrlMode(99), "unknown"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want, func(t *testing.T) {
|
||||
if got := tt.mode.String(); got != tt.want {
|
||||
t.Errorf("CtrlMode(%d).String() = %q, want %q", tt.mode, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCtrlMode_IotaValues(t *testing.T) {
|
||||
// Verify sequential iota values
|
||||
if int(miot.CtrlModeCloud) != 0 {
|
||||
t.Errorf("CtrlModeCloud = %d, want 0", miot.CtrlModeCloud)
|
||||
}
|
||||
if int(miot.CtrlModeGateway) != 1 {
|
||||
t.Errorf("CtrlModeGateway = %d, want 1", miot.CtrlModeGateway)
|
||||
}
|
||||
if int(miot.CtrlModeLAN) != 2 {
|
||||
t.Errorf("CtrlModeLAN = %d, want 2", miot.CtrlModeLAN)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// String Constants
|
||||
// ============================================================================
|
||||
|
||||
func TestSupportedPlatforms(t *testing.T) {
|
||||
if len(miot.SUPPORTED_PLATFORMS) == 0 {
|
||||
t.Error("SUPPORTED_PLATFORMS should not be empty")
|
||||
}
|
||||
mustContain := []string{"switch", "light", "sensor", "binary_sensor", "climate", "cover"}
|
||||
for _, p := range mustContain {
|
||||
t.Run("contains_"+p, func(t *testing.T) {
|
||||
found := false
|
||||
for _, sp := range miot.SUPPORTED_PLATFORMS {
|
||||
if sp == p {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("SUPPORTED_PLATFORMS should contain %q", p)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsupportedModels(t *testing.T) {
|
||||
if len(miot.UNSUPPORTED_MODELS) == 0 {
|
||||
t.Error("UNSUPPORTED_MODELS should not be empty")
|
||||
}
|
||||
// All entries should be non-empty strings
|
||||
for i, m := range miot.UNSUPPORTED_MODELS {
|
||||
if m == "" {
|
||||
t.Errorf("UNSUPPORTED_MODELS[%d] is empty", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudServers(t *testing.T) {
|
||||
mustHave := []string{"cn", "de", "us", "ru", "sg", "i2"}
|
||||
for _, k := range mustHave {
|
||||
t.Run("contains_"+k, func(t *testing.T) {
|
||||
if name, ok := miot.CLOUD_SERVERS[k]; !ok || name == "" {
|
||||
t.Errorf("CLOUD_SERVERS should contain key %q with non-empty name", k)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationLanguages(t *testing.T) {
|
||||
mustHave := map[string]string{
|
||||
"en": "English",
|
||||
"zh-Hans": "简体中文",
|
||||
"zh-Hant": "繁體中文",
|
||||
}
|
||||
for code, name := range mustHave {
|
||||
t.Run(code, func(t *testing.T) {
|
||||
if got, ok := miot.INTEGRATION_LANGUAGES[code]; !ok || got != name {
|
||||
t.Errorf("INTEGRATION_LANGUAGES[%q] = %q, want %q", code, got, name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportedCentralGatewayCtrl(t *testing.T) {
|
||||
if len(miot.SUPPORT_CENTRAL_GATEWAY_CTRL) == 0 {
|
||||
t.Error("SUPPORT_CENTRAL_GATEWAY_CTRL should not be empty")
|
||||
}
|
||||
// "cn" must be in the list
|
||||
found := false
|
||||
for _, r := range miot.SUPPORT_CENTRAL_GATEWAY_CTRL {
|
||||
if r == "cn" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("SUPPORT_CENTRAL_GATEWAY_CTRL should contain \"cn\"")
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Numeric Constants
|
||||
// ============================================================================
|
||||
|
||||
func TestNumericConstants(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value int
|
||||
min int
|
||||
max int
|
||||
}{
|
||||
{"MIHOME_HTTP_API_TIMEOUT", miot.MIHOME_HTTP_API_TIMEOUT, 1, 120},
|
||||
{"MIHOME_MQTT_KEEPALIVE", miot.MIHOME_MQTT_KEEPALIVE, 1, 3600},
|
||||
{"MIHOME_CERT_EXPIRE_MARGIN", miot.MIHOME_CERT_EXPIRE_MARGIN, 1, 3600 * 24 * 30},
|
||||
{"NETWORK_REFRESH_INTERVAL", miot.NETWORK_REFRESH_INTERVAL, 1, 3600},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.value < tt.min || tt.value > tt.max {
|
||||
t.Errorf("%s = %d, expected between %d and %d", tt.name, tt.value, tt.min, tt.max)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshConstants(t *testing.T) {
|
||||
if miot.RefreshPropsDelay <= 0 {
|
||||
t.Errorf("RefreshPropsDelay = %d, should be positive", miot.RefreshPropsDelay)
|
||||
}
|
||||
if miot.RefreshPropsRetryMax <= 0 {
|
||||
t.Errorf("RefreshPropsRetryMax = %d, should be positive", miot.RefreshPropsRetryMax)
|
||||
}
|
||||
if miot.RefreshPropsRetryDelay <= 0 {
|
||||
t.Errorf("RefreshPropsRetryDelay = %d, should be positive", miot.RefreshPropsRetryDelay)
|
||||
}
|
||||
if miot.RefreshCloudDevicesDelay <= 0 {
|
||||
t.Errorf("RefreshCloudDevicesDelay = %d, should be positive", miot.RefreshCloudDevicesDelay)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CA Certificate Constants
|
||||
// ============================================================================
|
||||
|
||||
func TestCA_Cert(t *testing.T) {
|
||||
if miot.MIHOME_CA_CERT_STR == "" {
|
||||
t.Error("MIHOME_CA_CERT_STR should not be empty")
|
||||
}
|
||||
if !strings.HasPrefix(miot.MIHOME_CA_CERT_STR, "-----BEGIN CERTIFICATE-----") {
|
||||
t.Error("MIHOME_CA_CERT_STR should start with BEGIN CERTIFICATE")
|
||||
}
|
||||
if len(miot.MIHOME_CA_CERT_SHA256) != 64 {
|
||||
t.Errorf("MIHOME_CA_CERT_SHA256 length = %d, want 64 (SHA256 hex)", len(miot.MIHOME_CA_CERT_SHA256))
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MQTT Topics
|
||||
// ============================================================================
|
||||
|
||||
func TestMQTTTopics(t *testing.T) {
|
||||
if miot.TopicProp != "properties_changed" {
|
||||
t.Errorf("TopicProp = %q, want %q", miot.TopicProp, "properties_changed")
|
||||
}
|
||||
if miot.TopicEvent != "event_occured" {
|
||||
t.Errorf("TopicEvent = %q, want %q", miot.TopicEvent, "event_occured")
|
||||
}
|
||||
if miot.TopicState != "state" {
|
||||
t.Errorf("TopicState = %q, want %q", miot.TopicState, "state")
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// API Paths
|
||||
// ============================================================================
|
||||
|
||||
func TestAPIPaths(t *testing.T) {
|
||||
if miot.APIPathGetDevices == "" {
|
||||
t.Error("APIPathGetDevices should not be empty")
|
||||
}
|
||||
if miot.APIPathGetProps == "" {
|
||||
t.Error("APIPathGetProps should not be empty")
|
||||
}
|
||||
if miot.APIPathSetProps == "" {
|
||||
t.Error("APIPathSetProps should not be empty")
|
||||
}
|
||||
if miot.APIPathDoAction == "" {
|
||||
t.Error("APIPathDoAction should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Default Values
|
||||
// ============================================================================
|
||||
|
||||
func TestDefaultValues(t *testing.T) {
|
||||
if miot.DefaultCloudServer != "cn" {
|
||||
t.Errorf("DefaultCloudServer = %q, want %q", miot.DefaultCloudServer, "cn")
|
||||
}
|
||||
if miot.DefaultOAUTH2APIHost == "" {
|
||||
t.Error("DefaultOAUTH2APIHost should not be empty")
|
||||
}
|
||||
if miot.MHomeHTTPAPITimeout <= 0 {
|
||||
t.Errorf("MHomeHTTPAPITimeout = %d, should be positive", miot.MHomeHTTPAPITimeout)
|
||||
}
|
||||
if miot.DefaultMQTTBroker == "" {
|
||||
t.Error("DefaultMQTTBroker should not be empty")
|
||||
}
|
||||
if miot.DefaultMQTTKeepAlive <= 0 {
|
||||
t.Errorf("DefaultMQTTKeepAlive = %d, should be positive", miot.DefaultMQTTKeepAlive)
|
||||
}
|
||||
if miot.DefaultMQTTQoS < 0 || miot.DefaultMQTTQoS > 2 {
|
||||
t.Errorf("DefaultMQTTQoS = %d, should be between 0 and 2", miot.DefaultMQTTQoS)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Domain and OAuth Constants
|
||||
// ============================================================================
|
||||
|
||||
func TestDomainConstants(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
}{
|
||||
{"DOMAIN", miot.DOMAIN},
|
||||
{"DEFAULT_NAME", miot.DEFAULT_NAME},
|
||||
{"OAUTH2_CLIENT_ID", miot.OAUTH2_CLIENT_ID},
|
||||
{"OAUTH2_AUTH_URL", miot.OAUTH2_AUTH_URL},
|
||||
{"OAUTH_REDIRECT_URL", miot.OAUTH_REDIRECT_URL},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.value == "" {
|
||||
t.Errorf("%s should not be empty", tt.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cover Dead Zone
|
||||
// ============================================================================
|
||||
|
||||
func TestCoverDeadZone(t *testing.T) {
|
||||
if miot.DEFAULT_COVER_DEAD_ZONE_WIDTH < miot.MIN_COVER_DEAD_ZONE_WIDTH {
|
||||
t.Errorf("DEFAULT_COVER_DEAD_ZONE_WIDTH (%d) < MIN (%d)",
|
||||
miot.DEFAULT_COVER_DEAD_ZONE_WIDTH, miot.MIN_COVER_DEAD_ZONE_WIDTH)
|
||||
}
|
||||
if miot.DEFAULT_COVER_DEAD_ZONE_WIDTH > miot.MAX_COVER_DEAD_ZONE_WIDTH {
|
||||
t.Errorf("DEFAULT_COVER_DEAD_ZONE_WIDTH (%d) > MAX (%d)",
|
||||
miot.DEFAULT_COVER_DEAD_ZONE_WIDTH, miot.MAX_COVER_DEAD_ZONE_WIDTH)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user