Add server-side rendered setup UI accessible via `heatwave web`. The dashboard is now re-rendered per request and includes a nav bar linking to the new /setup page. Setup provides full CRUD for profiles, rooms, devices, occupants, AC units (with room assignment), scenario toggles, and forecast fetching — all via POST/redirect/GET forms. - Add ShowNav field to DashboardData for conditional nav bar - Extract fetchForecastForProfile() for reuse by web handler - Create setup.html.tmpl with Tailwind-styled entity sections - Create web_handlers.go with 15 route handlers and flash cookies - Switch web.go from pre-rendered to per-request dashboard rendering - Graceful dashboard fallback when no forecast data exists
122 lines
3.2 KiB
Go
122 lines
3.2 KiB
Go
package llm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestNoopProvider(t *testing.T) {
|
|
n := NewNoop()
|
|
if n.Name() != "none" {
|
|
t.Errorf("Name = %s, want none", n.Name())
|
|
}
|
|
s, err := n.Summarize(context.Background(), SummaryInput{})
|
|
if err != nil || s != "" {
|
|
t.Errorf("Summarize = (%q, %v), want empty", s, err)
|
|
}
|
|
r, err := n.RewriteAction(context.Background(), ActionInput{})
|
|
if err != nil || r != "" {
|
|
t.Errorf("RewriteAction = (%q, %v), want empty", r, err)
|
|
}
|
|
h, err := n.GenerateHeatPlan(context.Background(), HeatPlanInput{})
|
|
if err != nil || h != "" {
|
|
t.Errorf("GenerateHeatPlan = (%q, %v), want empty", h, err)
|
|
}
|
|
}
|
|
|
|
func TestAnthropicProvider_MockServer(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get("x-api-key") != "test-key" {
|
|
t.Error("missing api key header")
|
|
}
|
|
if r.Header.Get("anthropic-version") != "2023-06-01" {
|
|
t.Error("missing anthropic-version header")
|
|
}
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"content": []map[string]string{
|
|
{"type": "text", "text": "- Bullet one\n- Bullet two\n- Bullet three"},
|
|
},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
a := NewAnthropic("test-key", "test-model", srv.Client())
|
|
a.baseURL = srv.URL
|
|
|
|
result, err := a.Summarize(context.Background(), testSummaryInput())
|
|
if err != nil {
|
|
t.Fatalf("Summarize: %v", err)
|
|
}
|
|
if result == "" {
|
|
t.Error("empty result")
|
|
}
|
|
}
|
|
|
|
func TestAnthropicProvider_Error(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"error": map[string]string{"message": "invalid api key"},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
a := NewAnthropic("bad-key", "", srv.Client())
|
|
a.baseURL = srv.URL
|
|
|
|
_, err := a.Summarize(context.Background(), SummaryInput{})
|
|
if err == nil {
|
|
t.Error("expected error")
|
|
}
|
|
}
|
|
|
|
func TestOpenAIProvider_MockServer(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !containsBearer(r.Header.Get("Authorization")) {
|
|
t.Error("missing bearer token")
|
|
}
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"choices": []map[string]any{
|
|
{"message": map[string]string{"content": "Test summary"}},
|
|
},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
o := NewOpenAI("test-key", "test-model", srv.Client())
|
|
o.baseURL = srv.URL
|
|
|
|
result, err := o.Summarize(context.Background(), testSummaryInput())
|
|
if err != nil {
|
|
t.Fatalf("Summarize: %v", err)
|
|
}
|
|
if result != "Test summary" {
|
|
t.Errorf("result = %q, want 'Test summary'", result)
|
|
}
|
|
}
|
|
|
|
func TestOllamaProvider_MockServer(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"message": map[string]string{"content": "Ollama summary"},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
o := NewOllama("test-model", srv.URL, srv.Client())
|
|
|
|
result, err := o.Summarize(context.Background(), testSummaryInput())
|
|
if err != nil {
|
|
t.Fatalf("Summarize: %v", err)
|
|
}
|
|
if result != "Ollama summary" {
|
|
t.Errorf("result = %q, want 'Ollama summary'", result)
|
|
}
|
|
}
|
|
|
|
func containsBearer(s string) bool {
|
|
return len(s) > 7 && s[:7] == "Bearer "
|
|
}
|