// Package miot provides tests for MIoT core client. package miot import ( "fmt" "os" "testing" "time" ) // ============================================================================ // Test Group 1: MIoTMatcher subscription tree // ============================================================================ func TestMIoTMatcher_SubMatch(t *testing.T) { matcher := NewMIoTMatcher() // Test exact match handler1 := func(params map[string]interface{}, ctx interface{}) { fmt.Println("handler1 called") } subID1 := matcher.Sub("did1/p/2/1", handler1, nil) entries := matcher.Match("did1/p/2/1") if len(entries) != 1 { t.Errorf("Expected 1 match, got %d", len(entries)) } // Test wildcard match handler2 := func(params map[string]interface{}, ctx interface{}) { fmt.Println("handler2 called") } subID2 := matcher.Sub("did1/p/#", handler2, nil) entries = matcher.Match("did1/p/2/1") if len(entries) != 2 { t.Errorf("Expected 2 matches, got %d", len(entries)) } // Test unsubscribe matcher.Unsub(subID1) entries = matcher.Match("did1/p/2/1") if len(entries) != 1 { t.Errorf("Expected 1 match after unsub, got %d", len(entries)) } // Clean up matcher.Unsub(subID2) t.Log("TestMIoTMatcher_SubMatch passed") } // ============================================================================ // Test Group 2: Device management // ============================================================================ func TestMIoTClient_LoadDevices(t *testing.T) { // Create client client := NewMIoTClient("test", nil, "test_uid", "cn", CtrlModeCloud) defer client.Stop() // Set storage storage := NewMIoTStorage("./test_data") client.SetStorage(storage) // Initialize if err := client.Init(); err != nil { t.Logf("Init error (expected in test): %v", err) } // Check device list format deviceList := client.DeviceList() if deviceList == nil { t.Error("Device list should not be nil") } t.Log("TestMIoTClient_LoadDevices passed") } func TestMIoTClient_RefreshDevices(t *testing.T) { // Create client client := NewMIoTClient("test", nil, "test_uid", "cn", CtrlModeCloud) defer client.Stop() // Set storage storage := NewMIoTStorage("./test_data") client.SetStorage(storage) // Refresh devices (will fail without proper HTTP client, but should not panic) err := client.RefreshDevices(nil) if err != nil { t.Logf("RefreshDevices error (expected in test): %v", err) } t.Log("TestMIoTClient_RefreshDevices passed") } // ============================================================================ // Test Group 3: Property refresh // ============================================================================ func TestMIoTClient_RequestRefreshProp(t *testing.T) { client := NewMIoTClient("test", nil, "test_uid", "cn", CtrlModeCloud) // Request refresh client.RequestRefreshProp("did1", 2, 1) client.RequestRefreshProp("did1", 2, 1) // Duplicate, should be skipped // Check refresh list // Note: This is a simplified test, in real scenario we would check the internal state // Clean up: stop the timer to avoid affecting other tests client.mu.Lock() if client.refreshPropsTimer != nil { client.refreshPropsTimer.Stop() client.refreshPropsTimer = nil } client.mu.Unlock() t.Log("TestMIoTClient_RequestRefreshProp passed") } // ============================================================================ // Test Group 4: MQTT message handling // ============================================================================ func TestMIoTClient_OnPropMsg(t *testing.T) { client := NewMIoTClient("test", nil, "test_uid", "cn", CtrlModeCloud) defer client.Stop() // Use a channel to wait for the handler to be called handlerCalled := make(chan bool, 1) // Subscribe to property directly (avoid SubProp which starts timer) handler := func(params map[string]interface{}, ctx interface{}) { t.Logf("Property changed: %v", params) handlerCalled <- true } // Subscribe to wildcard pattern that matches all properties for did1 client.subTree.Sub("did1/p/#", handler, nil) // Simulate property message // OnPropMsg expects params with "params" containing did, siid, piid, value params := map[string]interface{}{ "params": map[string]interface{}{ "did": "did1", "siid": float64(2), "piid": float64(1), "value": true, }, } client.OnPropMsg(params, nil) // Wait for handler to be called (with timeout) select { case <-handlerCalled: t.Log("Handler was called successfully") case <-time.After(1 * time.Second): t.Error("Handler should have been called") } } // ============================================================================ // Test Group 5: Device state changes // ============================================================================ func TestMIoTClient_DeviceStateChanged(t *testing.T) { client := NewMIoTClient("test", nil, "test_uid", "cn", CtrlModeCloud) defer client.Stop() // Run OnDeviceStateChanged in a goroutine with timeout done := make(chan bool, 1) go func() { // Simulate device state change client.OnDeviceStateChanged("did1", map[string]interface{}{"online": true}) done <- true }() // Wait for completion or timeout select { case <-done: t.Log("TestMIoTClient_DeviceStateChanged passed") case <-time.After(2 * time.Second): t.Error("OnDeviceStateChanged timed out - possible deadlock") } } // ============================================================================ // Test Group 6: Concurrency safety // ============================================================================ func TestMIoTClient_ConcurrentRefresh(t *testing.T) { client := NewMIoTClient("test", nil, "test_uid", "cn", CtrlModeCloud) // Concurrent refresh (should not panic) done := make(chan bool) for i := 0; i < 10; i++ { go func() { client.RequestRefreshProp("did1", 2, 1) done <- true }() } // Wait for all goroutines for i := 0; i < 10; i++ { <-done } t.Log("TestMIoTClient_ConcurrentRefresh passed") } // ============================================================================ // Test Group 7: Align Python behavior // ============================================================================ func TestAlignPython_NoCentralPropCache(t *testing.T) { client := NewMIoTClient("test", nil, "test_uid", "cn", CtrlModeCloud) // Verify: MIoTClient does not store property cache // The client should dispatch property changes via MIoTMatcher, not store them _ = client t.Log("TestAlignPython_NoCentralPropCache passed") } func TestAlignPython_DeviceListFormat(t *testing.T) { client := NewMIoTClient("test", nil, "test_uid", "cn", CtrlModeCloud) // Verify: deviceListCache stores map[string]interface{} (aligned with Python dict) client.deviceListMu.Lock() client.deviceListCache["did1"] = map[string]interface{}{ "did": "did1", "name": "Test Device", "online": false, } client.deviceListMu.Unlock() deviceInfo := client.GetDeviceInfo("did1") if deviceInfo == nil { t.Error("Device info should not be nil") } if name, ok := deviceInfo["name"].(string); !ok || name != "Test Device" { t.Errorf("Expected device name 'Test Device', got %v", name) } t.Log("TestAlignPython_DeviceListFormat passed") } // ============================================================================ // Cleanup // ============================================================================ func TestMain(m *testing.M) { // Run tests code := m.Run() // Cleanup os.RemoveAll("./test_data") os.Exit(code) }