package main // query_firmware_active_tasks_test.go — M9-4.5:firmware 查詢 helper 測試 // // 覆蓋情境: // 1. port <= 0 → 立即 return hasActive=false(fail-open 預設值) // 2. server 正常回 hasActive=true + tasks → 解析正確 // 3. server 正常回 hasActive=false + 空 tasks → tasks 非 nil(避免 caller 處理 nil panic) // 4. server 回 500 → fail-open(hasActive=false)+ error // 5. server timeout → fail-open + error(不卡 Wails 關閉流程) // 6. server 回非預期 JSON → fail-open + error import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "net/url" "strconv" "testing" "time" ) func portFromTestServerURL(t *testing.T, raw string) int { t.Helper() u, err := url.Parse(raw) if err != nil { t.Fatalf("parse test server url: %v", err) } p, err := strconv.Atoi(u.Port()) if err != nil { t.Fatalf("port atoi: %v", err) } return p } // ────────────────────────────────────────────────────────────────────── // 1. port <= 0 → 立即 fail-open // ────────────────────────────────────────────────────────────────────── func TestQueryFirmwareActiveTasks_PortZero(t *testing.T) { res, err := queryFirmwareActiveTasks(context.Background(), 0) if err == nil { t.Errorf("expected error when port=0") } if res.HasActive { t.Errorf("expected hasActive=false on port=0") } } func TestQueryFirmwareActiveTasks_PortNegative(t *testing.T) { res, err := queryFirmwareActiveTasks(context.Background(), -1) if err == nil { t.Errorf("expected error when port<0") } if res.HasActive { t.Errorf("expected hasActive=false on port<0") } } // ────────────────────────────────────────────────────────────────────── // 2. server 正常回 hasActive=true + tasks // ────────────────────────────────────────────────────────────────────── func TestQueryFirmwareActiveTasks_HasActiveWithTasks(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/firmware/active-tasks" { t.Errorf("unexpected path: %s", r.URL.Path) } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "data": map[string]interface{}{ "hasActive": true, "tasks": []map[string]interface{}{ { "taskId": "upgrade-KL520-0", "deviceId": "kl520-0", "deviceName": "KL520 #1", "chip": "KL520", "direction": "upgrade", "stage": "flashing", "elapsedMs": int64(12000), "etaSeconds": 45, }, }, }, }) })) defer srv.Close() port := portFromTestServerURL(t, srv.URL) res, err := queryFirmwareActiveTasks(context.Background(), port) if err != nil { t.Fatalf("unexpected error: %v", err) } if !res.HasActive { t.Errorf("expected hasActive=true") } if len(res.Tasks) != 1 { t.Fatalf("expected 1 task, got %d", len(res.Tasks)) } tk := res.Tasks[0] if tk.TaskID != "upgrade-KL520-0" || tk.DeviceID != "kl520-0" || tk.Chip != "KL520" { t.Errorf("task decoded wrong: %+v", tk) } if tk.Direction != "upgrade" || tk.Stage != "flashing" { t.Errorf("task direction/stage decoded wrong: %+v", tk) } if tk.EtaSeconds != 45 { t.Errorf("expected etaSeconds=45, got %d", tk.EtaSeconds) } } // ────────────────────────────────────────────────────────────────────── // 3. server 回 hasActive=false + null tasks → tasks 非 nil // ────────────────────────────────────────────────────────────────────── func TestQueryFirmwareActiveTasks_HasActiveFalseNullTasks(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "data": map[string]interface{}{ "hasActive": false, "tasks": nil, }, }) })) defer srv.Close() port := portFromTestServerURL(t, srv.URL) res, err := queryFirmwareActiveTasks(context.Background(), port) if err != nil { t.Fatalf("unexpected error: %v", err) } if res.HasActive { t.Errorf("expected hasActive=false") } if res.Tasks == nil { t.Errorf("expected Tasks to be non-nil (empty slice), got nil") } } // ────────────────────────────────────────────────────────────────────── // 4. server 回 500 → fail-open // ────────────────────────────────────────────────────────────────────── func TestQueryFirmwareActiveTasks_ServerError500(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = fmt.Fprintln(w, "internal error") })) defer srv.Close() port := portFromTestServerURL(t, srv.URL) res, err := queryFirmwareActiveTasks(context.Background(), port) if err == nil { t.Errorf("expected error on 500") } if res.HasActive { t.Errorf("expected hasActive=false on 500 (fail-open)") } } // ────────────────────────────────────────────────────────────────────── // 5. server timeout → fail-open + error // ────────────────────────────────────────────────────────────────────── func TestQueryFirmwareActiveTasks_Timeout(t *testing.T) { // 縮短 helper timeout origTimeout := queryFirmwareTasksTimeout origClient := queryFirmwareTasksClient queryFirmwareTasksTimeout = 100 * time.Millisecond queryFirmwareTasksClient = &http.Client{Timeout: queryFirmwareTasksTimeout} defer func() { queryFirmwareTasksTimeout = origTimeout queryFirmwareTasksClient = origClient }() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(500 * time.Millisecond) w.WriteHeader(http.StatusOK) })) defer srv.Close() port := portFromTestServerURL(t, srv.URL) done := make(chan struct{}) go func() { res, err := queryFirmwareActiveTasks(context.Background(), port) if err == nil { t.Errorf("expected timeout error") } if res.HasActive { t.Errorf("expected hasActive=false on timeout (fail-open)") } close(done) }() select { case <-done: // ok case <-time.After(2 * time.Second): t.Fatalf("queryFirmwareActiveTasks blocked > 2s, timeout not respected") } } // ────────────────────────────────────────────────────────────────────── // 6. server 回非預期 JSON → fail-open + error // ────────────────────────────────────────────────────────────────────── func TestQueryFirmwareActiveTasks_MalformedJSON(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte("{this is not json")) })) defer srv.Close() port := portFromTestServerURL(t, srv.URL) res, err := queryFirmwareActiveTasks(context.Background(), port) if err == nil { t.Errorf("expected decode error on malformed JSON") } if res.HasActive { t.Errorf("expected hasActive=false on decode error (fail-open)") } } // ────────────────────────────────────────────────────────────────────── // 7. success=false 即使 200 → 視為 error // ────────────────────────────────────────────────────────────────────── func TestQueryFirmwareActiveTasks_SuccessFalse(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "data": map[string]interface{}{"hasActive": false}, }) })) defer srv.Close() port := portFromTestServerURL(t, srv.URL) res, err := queryFirmwareActiveTasks(context.Background(), port) if err == nil { t.Errorf("expected error when success=false") } if res.HasActive { t.Errorf("expected hasActive=false (fail-open)") } }