Files
HeatGuard/internal/bettervent/provider_test.go
vikingowl a334dd57a0 feat: integrate bettervent.me device database and add BTU/kW unit switcher
Add bettervent.me provider with lazy-cached device list (~6,700 Eurovent-certified
heat pumps), search API endpoint, device search UI with auto-populate, BTU/kW unit
switcher for European users, and extended AC fields (SEER, SCOP, COP, TOL, Tbiv,
refrigerant). Closes #2.
2026-02-11 00:31:39 +01:00

179 lines
4.4 KiB
Go

package bettervent
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
var testDevices = []apiDevice{
{
ID: 1, TradeName: "MITSUBISHI ELECTRIC", ModelName: "MSZ-AP25VGK",
Range: "MSZ-AP series", Pc: 2.5, Ph: 3.2, EER: 4.0, COP: 4.6,
SEER: 8.7, SEERClass: "A+++", SCOP: 4.6, SCOPClass: "A++",
TOL: -25, Tbiv: -15, Mounting: "Wall mounted", Refrigerant: "R32",
},
{
ID: 2, TradeName: "DAIKIN", ModelName: "FTXM25R",
Range: "Perfera", Pc: 2.5, Ph: 3.0, EER: 3.8, COP: 4.5,
SEER: 8.73, SEERClass: "A+++", SCOP: 5.1, SCOPClass: "A+++",
TOL: -25, Tbiv: -15, Mounting: "Wall mounted", Refrigerant: "R32",
},
{
ID: 3, TradeName: "MITSUBISHI ELECTRIC", ModelName: "MFZ-KT50VG",
Range: "Floor standing", Pc: 5.0, Ph: 5.8, EER: 3.5, COP: 4.2,
SEER: 7.0, SEERClass: "A++", SCOP: 4.3, SCOPClass: "A++",
TOL: -15, Tbiv: -7, Mounting: "Floor standing", Refrigerant: "R410A",
},
}
func newTestServer(t *testing.T) *httptest.Server {
t.Helper()
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/devices" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(testDevices)
}))
}
func TestSearch_MatchesByTradeName(t *testing.T) {
ts := newTestServer(t)
defer ts.Close()
p := NewWithBaseURL(nil, ts.URL)
resp, err := p.Search(context.Background(), "daikin")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Total != 1 {
t.Fatalf("got %d results, want 1", resp.Total)
}
if resp.Devices[0].TradeName != "DAIKIN" {
t.Errorf("got trade name %q, want DAIKIN", resp.Devices[0].TradeName)
}
if resp.Devices[0].CoolingKW != 2.5 {
t.Errorf("got cooling %v kW, want 2.5", resp.Devices[0].CoolingKW)
}
}
func TestSearch_MatchesByModelName(t *testing.T) {
ts := newTestServer(t)
defer ts.Close()
p := NewWithBaseURL(nil, ts.URL)
resp, err := p.Search(context.Background(), "MSZ-AP25")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Total != 1 {
t.Fatalf("got %d results, want 1", resp.Total)
}
if resp.Devices[0].ID != 1 {
t.Errorf("got ID %d, want 1", resp.Devices[0].ID)
}
}
func TestSearch_MultipleMatches(t *testing.T) {
ts := newTestServer(t)
defer ts.Close()
p := NewWithBaseURL(nil, ts.URL)
resp, err := p.Search(context.Background(), "mitsubishi")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Total != 2 {
t.Fatalf("got %d results, want 2", resp.Total)
}
}
func TestSearch_EmptyQuery(t *testing.T) {
ts := newTestServer(t)
defer ts.Close()
p := NewWithBaseURL(nil, ts.URL)
resp, err := p.Search(context.Background(), "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Total != 0 {
t.Errorf("got %d results for empty query, want 0", resp.Total)
}
}
func TestSearch_NoResults(t *testing.T) {
ts := newTestServer(t)
defer ts.Close()
p := NewWithBaseURL(nil, ts.URL)
resp, err := p.Search(context.Background(), "nonexistent-brand-xyz")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Total != 0 {
t.Errorf("got %d results, want 0", resp.Total)
}
}
func TestSearch_CacheReuse(t *testing.T) {
callCount := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
callCount++
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(testDevices)
}))
defer ts.Close()
p := NewWithBaseURL(nil, ts.URL)
ctx := context.Background()
_, _ = p.Search(ctx, "daikin")
_, _ = p.Search(ctx, "mitsubishi")
if callCount != 1 {
t.Errorf("expected 1 API call (cached), got %d", callCount)
}
}
func TestSearch_FieldMapping(t *testing.T) {
ts := newTestServer(t)
defer ts.Close()
p := NewWithBaseURL(nil, ts.URL)
resp, err := p.Search(context.Background(), "FTXM25R")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Total != 1 {
t.Fatalf("got %d results, want 1", resp.Total)
}
d := resp.Devices[0]
if d.SEER != 8.73 {
t.Errorf("SEER = %v, want 8.73", d.SEER)
}
if d.SEERClass != "A+++" {
t.Errorf("SEERClass = %q, want A+++", d.SEERClass)
}
if d.SCOP != 5.1 {
t.Errorf("SCOP = %v, want 5.1", d.SCOP)
}
if d.TOL != -25 {
t.Errorf("TOL = %v, want -25", d.TOL)
}
if d.Tbiv != -15 {
t.Errorf("Tbiv = %v, want -15", d.Tbiv)
}
if d.Refrigerant != "R32" {
t.Errorf("Refrigerant = %q, want R32", d.Refrigerant)
}
if d.Mounting != "Wall mounted" {
t.Errorf("Mounting = %q, want Wall mounted", d.Mounting)
}
}