chore: upgrade Go to 1.27 (#30601)

This commit is contained in:
andig 2026-08-20 13:36:53 +02:00 • committed by GitHub
parent 748d36d130
commit e63bae0c8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 135 additions and 154 deletions

View file

@ -13,7 +13,7 @@ inputs:
runs: runs:
using: composite using: composite
steps: steps:
- uses: actions/setup-go@v6 - uses: actions/setup-go@v7
with: with:
go-version-file: go.mod go-version-file: go.mod
cache: false # managed explicitly below to keep the scoped namespaces cache: false # managed explicitly below to keep the scoped namespaces

View file

@ -23,6 +23,7 @@ linters:
settings: settings:
modernize: modernize:
disable: disable:
- embedlit
- stringsbuilder - stringsbuilder
staticcheck: staticcheck:
checks: checks:

View file

@ -24,7 +24,7 @@ RUN make ui
# STEP 2 build executable binary # STEP 2 build executable binary
FROM --platform=$BUILDPLATFORM golang:1.26.3-alpine AS builder FROM --platform=$BUILDPLATFORM golang:1.27.0-alpine AS builder
# Install git + SSL ca certificates. # Install git + SSL ca certificates.
# Git is required for fetching the dependencies. # Git is required for fetching the dependencies.

View file

@ -10,10 +10,10 @@ import (
) )
func TestEvseWifi(t *testing.T) { func TestEvseWifi(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintln(w, `{"list":[{"useMeter":true, "alwaysActive":true}]}`) _, _ = fmt.Fprintln(w, `{"list":[{"useMeter":true, "alwaysActive":true}]}`)
})) }))
defer ts.Close() ts.Start()
wb, err := NewEVSEWifiFromConfig(map[string]any{ wb, err := NewEVSEWifiFromConfig(map[string]any{
"uri": ts.URL, "uri": ts.URL,
@ -46,10 +46,10 @@ func TestEvseWifi(t *testing.T) {
} }
func TestEvseWifiEx(t *testing.T) { func TestEvseWifiEx(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintln(w, `{"list":[{"actualCurrentMA":600, "alwaysActive":true}]}`) _, _ = fmt.Fprintln(w, `{"list":[{"actualCurrentMA":600, "alwaysActive":true}]}`)
})) }))
defer ts.Close() ts.Start()
wb, err := NewEVSEWifiFromConfig(map[string]any{ wb, err := NewEVSEWifiFromConfig(map[string]any{
"uri": ts.URL, "uri": ts.URL,

View file

@ -16,13 +16,11 @@ import (
func TestTokenSource_ContextCancellation(t *testing.T) { func TestTokenSource_ContextCancellation(t *testing.T) {
// server that blocks -- simulates slow/unreachable wallbox // server that blocks -- simulates slow/unreachable wallbox
unblock := make(chan struct{}) unblock := make(chan struct{})
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
<-unblock <-unblock
})) }))
defer func() { srv.StartTLS()
close(unblock) defer close(unblock)
srv.Close()
}()
log := util.NewLogger("test") log := util.NewLogger("test")
@ -40,12 +38,12 @@ func TestTokenSource_ContextCancellation(t *testing.T) {
func TestTokenSource_Success(t *testing.T) { func TestTokenSource_Success(t *testing.T) {
// server that returns a valid JWT-style token // server that returns a valid JWT-style token
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Authorization", fmt.Sprintf("Bearer %s", w.Header().Set("Authorization", fmt.Sprintf("Bearer %s",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjo5OTk5OTk5OTk5fQ.signature")) "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjo5OTk5OTk5OTk5fQ.signature"))
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
})) }))
defer srv.Close() srv.StartTLS()
log := util.NewLogger("test") log := util.NewLogger("test")

View file

@ -34,7 +34,8 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func TestLocalV1(t *testing.T) { func TestLocalV1(t *testing.T) {
h := &handler{} h := &handler{}
srv := httptest.NewServer(h) srv := httptest.NewTestServer(t, h)
srv.Start()
// h.expect("/api/status?filter=alw") // h.expect("/api/status?filter=alw")
local := NewLocal(util.NewLogger("foo"), srv.URL, 0) local := NewLocal(util.NewLogger("foo"), srv.URL, 0)
@ -52,7 +53,8 @@ func TestLocalV1(t *testing.T) {
func TestLocalV2(t *testing.T) { func TestLocalV2(t *testing.T) {
h := &handler{} h := &handler{}
srv := httptest.NewServer(h) srv := httptest.NewTestServer(t, h)
srv.Start()
h.expect("/api/status?filter=alw") h.expect("/api/status?filter=alw")
local := NewLocal(util.NewLogger("foo"), srv.URL, 0) local := NewLocal(util.NewLogger("foo"), srv.URL, 0)

View file

@ -33,7 +33,8 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
func TestGoEV1(t *testing.T) { func TestGoEV1(t *testing.T) {
srv := httptest.NewServer(new(handler)) srv := httptest.NewTestServer(t, new(handler))
srv.Start()
sponsor.Subject = "foo" sponsor.Subject = "foo"
@ -56,8 +57,10 @@ func TestGoEV1(t *testing.T) {
} }
func TestGoEV2(t *testing.T) { func TestGoEV2(t *testing.T) {
srv := httptest.NewServer(new(handler)) h := new(handler)
srv.Config.Handler.(*handler).expect("/api/status?filter=alw") srv := httptest.NewTestServer(t, h)
srv.Start()
h.expect("/api/status?filter=alw")
sponsor.Subject = "foo" sponsor.Subject = "foo"

View file

@ -109,7 +109,7 @@ func TestForwarderUpstreamReconnect(t *testing.T) {
var conns atomic.Int32 var conns atomic.Int32
frames := make(chan []byte, 1) frames := make(chan []byte, 1)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{Subprotocols: []string{"ocpp1.6"}}) c, err := websocket.Accept(w, r, &websocket.AcceptOptions{Subprotocols: []string{"ocpp1.6"}})
if err != nil { if err != nil {
return return
@ -127,7 +127,7 @@ func TestForwarderUpstreamReconnect(t *testing.T) {
} }
c.CloseNow() c.CloseNow()
})) }))
defer srv.Close() srv.Start()
cleanup := forwarderTestSetup(t, id) cleanup := forwarderTestSetup(t, id)
defer cleanup() defer cleanup()
@ -144,7 +144,7 @@ func TestForwarderDialRetry(t *testing.T) {
var reqs atomic.Int32 var reqs atomic.Int32
frames := make(chan []byte, 1) frames := make(chan []byte, 1)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if reqs.Add(1) <= 2 { if reqs.Add(1) <= 2 {
// reject the websocket upgrade to simulate an unreachable upstream // reject the websocket upgrade to simulate an unreachable upstream
http.Error(w, "unavailable", http.StatusServiceUnavailable) http.Error(w, "unavailable", http.StatusServiceUnavailable)
@ -162,7 +162,7 @@ func TestForwarderDialRetry(t *testing.T) {
} }
c.CloseNow() c.CloseNow()
})) }))
defer srv.Close() srv.Start()
cleanup := forwarderTestSetup(t, id) cleanup := forwarderTestSetup(t, id)
defer cleanup() defer cleanup()

View file

@ -251,8 +251,8 @@ func TestSEMPCharger(t *testing.T) {
planningResponse: mockPlanningRequestResponse, planningResponse: mockPlanningRequestResponse,
infoResponse: mockDeviceInfoResponse, infoResponse: mockDeviceInfoResponse,
} }
server := httptest.NewServer(handler) server := httptest.NewTestServer(t, handler)
defer server.Close() server.Start()
ctx := t.Context() ctx := t.Context()
@ -318,8 +318,8 @@ func TestSEMPChargerOff(t *testing.T) {
planningResponse: mockEmptyPlanningRequestResponse, planningResponse: mockEmptyPlanningRequestResponse,
infoResponse: mockDeviceInfoResponse, infoResponse: mockDeviceInfoResponse,
} }
server := httptest.NewServer(handler) server := httptest.NewTestServer(t, handler)
defer server.Close() server.Start()
ctx := t.Context() ctx := t.Context()
@ -353,8 +353,8 @@ func TestSEMPChargerDeviceNotFound(t *testing.T) {
planningResponse: mockPlanningRequestResponse, planningResponse: mockPlanningRequestResponse,
infoResponse: mockDeviceInfoResponse, infoResponse: mockDeviceInfoResponse,
} }
server := httptest.NewServer(handler) server := httptest.NewTestServer(t, handler)
defer server.Close() server.Start()
// NewSEMP now calls Enabled() which will fail if device is not found // NewSEMP now calls Enabled() which will fail if device is not found
_, err := NewSEMP(t.Context(), server.URL+"/semp", "F-12345678-ABCDEF123456-00", time.Second) _, err := NewSEMP(t.Context(), server.URL+"/semp", "F-12345678-ABCDEF123456-00", time.Second)
@ -368,8 +368,8 @@ func TestSEMPChargerReady(t *testing.T) {
planningResponse: mockPlanningRequestResponse, planningResponse: mockPlanningRequestResponse,
infoResponse: mockDeviceInfoResponse, infoResponse: mockDeviceInfoResponse,
} }
server := httptest.NewServer(handler) server := httptest.NewTestServer(t, handler)
defer server.Close() server.Start()
ctx := t.Context() ctx := t.Context()
@ -396,8 +396,8 @@ func TestSEMPChargerPhases1p3p(t *testing.T) {
planningResponse: mockPlanningRequestResponse, planningResponse: mockPlanningRequestResponse,
infoResponse: mockDeviceInfoPhases1p3pResponse, infoResponse: mockDeviceInfoPhases1p3pResponse,
} }
server := httptest.NewServer(handler) server := httptest.NewTestServer(t, handler)
defer server.Close() server.Start()
ctx := t.Context() ctx := t.Context()
@ -440,8 +440,8 @@ func TestSEMPChargerChargedEnergy(t *testing.T) {
infoResponse: mockDeviceInfoResponse, infoResponse: mockDeviceInfoResponse,
parametersResponse: mockParametersResponse, parametersResponse: mockParametersResponse,
} }
server := httptest.NewServer(handler) server := httptest.NewTestServer(t, handler)
defer server.Close() server.Start()
ctx := t.Context() ctx := t.Context()
@ -465,8 +465,8 @@ func TestSEMPChargerChargedEnergy(t *testing.T) {
infoResponse: mockDeviceInfoResponse, infoResponse: mockDeviceInfoResponse,
// parametersResponse left empty // parametersResponse left empty
} }
server2 := httptest.NewServer(handler2) server2 := httptest.NewTestServer(t, handler2)
defer server2.Close() server2.Start()
wb2, err := NewSEMP(t.Context(), server2.URL+"/semp", "F-12345678-ABCDEF123456-00", time.Second) wb2, err := NewSEMP(t.Context(), server2.URL+"/semp", "F-12345678-ABCDEF123456-00", time.Second)
require.NoError(t, err) require.NoError(t, err)
@ -483,8 +483,8 @@ func TestSEMPChargerAutoDetectDeviceID(t *testing.T) {
planningResponse: mockPlanningRequestResponse, planningResponse: mockPlanningRequestResponse,
infoResponse: mockDeviceInfoResponse, infoResponse: mockDeviceInfoResponse,
} }
server := httptest.NewServer(handler) server := httptest.NewTestServer(t, handler)
defer server.Close() server.Start()
ctx := t.Context() ctx := t.Context()

View file

@ -20,10 +20,13 @@ func withSponsor(t *testing.T) {
t.Cleanup(func() { sponsor.Subject = orig }) t.Cleanup(func() { sponsor.Subject = orig })
} }
func trydanTestServerWithBody(body string) *httptest.Server { func trydanTestServerWithBody(t *testing.T, body string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Helper()
srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, body) fmt.Fprint(w, body)
})) }))
srv.Start()
return srv
} }
// ChargeState maps directly to api.ChargeStatus, except firmware 2.5.0 keeps it at // ChargeState maps directly to api.ChargeStatus, except firmware 2.5.0 keeps it at
@ -47,8 +50,7 @@ func TestTrydanStatus(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
srv := trydanTestServerWithBody(tc.json) srv := trydanTestServerWithBody(t, tc.json)
defer srv.Close()
wb, err := NewTrydan(srv.URL, 0) wb, err := NewTrydan(srv.URL, 0)
if err != nil { if err != nil {
@ -107,8 +109,7 @@ func TestTrydanPhaseMeasurementsUnavailable(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
srv := trydanTestServerWithBody(tc.json) srv := trydanTestServerWithBody(t, tc.json)
defer srv.Close()
wb, err := NewTrydan(srv.URL, 0) wb, err := NewTrydan(srv.URL, 0)
if err != nil { if err != nil {

4
go.mod
View file

@ -1,6 +1,6 @@
module github.com/evcc-io/evcc module github.com/evcc-io/evcc
go 1.26.3 go 1.27.0
require ( require (
dario.cat/mergo v1.0.2 dario.cat/mergo v1.0.2
@ -45,7 +45,6 @@ require (
github.com/gokrazy/updater v0.0.0-20260620140544-0a84d8ab3878 github.com/gokrazy/updater v0.0.0-20260620140544-0a84d8ab3878
github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/go-github/v32 v32.1.0 github.com/google/go-github/v32 v32.1.0
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2 github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/gosimple/slug v1.15.0 github.com/gosimple/slug v1.15.0
@ -175,6 +174,7 @@ require (
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/jsonschema-go v0.4.3 // indirect github.com/google/jsonschema-go v0.4.3 // indirect
github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/renameio/v2 v2.0.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect github.com/gosimple/unidecode v1.0.1 // indirect
github.com/govalues/decimal v0.1.36 // indirect github.com/govalues/decimal v0.1.36 // indirect

View file

@ -8,13 +8,13 @@ import (
"net/http" "net/http"
"strings" "strings"
"time" "time"
"uuid"
"github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/loadpoint" "github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/core/site" "github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/machine" "github.com/evcc-io/evcc/util/machine"
"github.com/google/uuid"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/koron/go-ssdp" "github.com/koron/go-ssdp"
) )
@ -56,10 +56,7 @@ func NewFromConfig(cfg Config, hostUri string, site site.API, addr string, route
return fmt.Errorf("invalid vendor id: %v. Must be 8 characters HEX string", vendorId) return fmt.Errorf("invalid vendor id: %v. Must be 8 characters HEX string", vendorId)
} }
uid, err := uuid.NewUUID() uid := uuid.New()
if err != nil {
return err
}
// Only if DeviceSerial is explicitly configured: validate it and patch the // Only if DeviceSerial is explicitly configured: validate it and patch the
// UUID node (last 6 bytes) to ensure the UDN and DeviceSerial are stable across restarts. // UUID node (last 6 bytes) to ensure the UDN and DeviceSerial are stable across restarts.
@ -80,6 +77,7 @@ func NewFromConfig(cfg Config, hostUri string, site site.API, addr string, route
} }
var did []byte var did []byte
var err error
if cfg.DeviceId == "" { if cfg.DeviceId == "" {
if did, err = UniqueDeviceID(); err != nil { if did, err = UniqueDeviceID(); err != nil {
return fmt.Errorf("creating device id: %w", err) return fmt.Errorf("creating device id: %w", err)

View file

@ -266,7 +266,7 @@ func TestDsmrWebSocket(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
payload := dsmrFrame(dsmrTelegram50) payload := dsmrFrame(dsmrTelegram50)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := websocket.Accept(w, r, nil) conn, err := websocket.Accept(w, r, nil)
if err != nil { if err != nil {
return return
@ -280,7 +280,7 @@ func TestDsmrWebSocket(t *testing.T) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
} }
})) }))
defer srv.Close() srv.Start()
uri := "ws" + strings.TrimPrefix(srv.URL, "http") uri := "ws" + strings.TrimPrefix(srv.URL, "http")

View file

@ -186,7 +186,7 @@ func (c *EEBus) lastCurtailPercent() int {
return c.curtailPercent return c.curtailPercent
} }
func (c *EEBus) readValue(scenario uint, update func(entity spineapi.EntityRemoteInterface) (float64, error)) (float64, error) { func (c *EEBus) readValue[T any](scenario uint, update func(entity spineapi.EntityRemoteInterface) (T, error)) (T, error) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
return eebusReadValue(c.mm, c.maEntity, scenario, update) return eebusReadValue(c.mm, c.maEntity, scenario, update)
@ -205,10 +205,7 @@ func (c *EEBus) TotalEnergy() (float64, error) {
} }
func (c *EEBus) readPhases(scenario uint, update func(entity spineapi.EntityRemoteInterface) ([]float64, error)) (float64, float64, float64, error) { func (c *EEBus) readPhases(scenario uint, update func(entity spineapi.EntityRemoteInterface) ([]float64, error)) (float64, float64, float64, error) {
c.mu.Lock() res, err := c.readValue(scenario, update)
defer c.mu.Unlock()
res, err := eebusReadValue(c.mm, c.maEntity, scenario, update)
if err != nil { if err != nil {
return 0, 0, 0, err return 0, 0, 0, err
} }

View file

@ -108,7 +108,7 @@ type gen2 struct {
emdata func() (Gen2EMData, error) emdata func() (Gen2EMData, error)
} }
func apiCall[T any](c *gen2, id int, method string) func() (T, error) { func (c *gen2) apiCall[T any](id int, method string) func() (T, error) {
return func() (T, error) { return func() (T, error) {
var res T var res T
if err := c.execCmd(id, method, &res); err != nil { if err := c.execCmd(id, method, &res); err != nil {
@ -152,9 +152,9 @@ func newGen2(helper *request.Helper, uri, model string, channel int, user, passw
} }
if c.hasMethod("PM1.GetStatus") { if c.hasMethod("PM1.GetStatus") {
c.switchstatus = util.ResettableCached(apiCall[Gen2SwitchStatus](c, channel, "PM1.GetStatus"), cache) c.switchstatus = util.ResettableCached(c.apiCall[Gen2SwitchStatus](channel, "PM1.GetStatus"), cache)
} else { } else {
c.switchstatus = util.ResettableCached(apiCall[Gen2SwitchStatus](c, c.switchchannel, "Switch.GetStatus"), cache) c.switchstatus = util.ResettableCached(c.apiCall[Gen2SwitchStatus](c.switchchannel, "Switch.GetStatus"), cache)
} }
// device-side "Reverse power measurement" setting (requires restart, hence static) // device-side "Reverse power measurement" setting (requires restart, hence static)
var cfgMethod string var cfgMethod string
@ -177,10 +177,10 @@ func newGen2(helper *request.Helper, uri, model string, channel int, user, passw
c.reversed = cfg.Reverse c.reversed = cfg.Reverse
} }
c.em1status = util.Cached(apiCall[Gen2EM1Status](c, channel, "EM1.GetStatus"), cache) c.em1status = util.Cached(c.apiCall[Gen2EM1Status](channel, "EM1.GetStatus"), cache)
c.em1data = util.Cached(apiCall[Gen2EM1Data](c, channel, "EM1Data.GetStatus"), cache) c.em1data = util.Cached(c.apiCall[Gen2EM1Data](channel, "EM1Data.GetStatus"), cache)
c.emstatus = util.Cached(apiCall[Gen2EMStatus](c, channel, "EM.GetStatus"), cache) c.emstatus = util.Cached(c.apiCall[Gen2EMStatus](channel, "EM.GetStatus"), cache)
c.emdata = util.Cached(apiCall[Gen2EMData](c, channel, "EMData.GetStatus"), cache) c.emdata = util.Cached(c.apiCall[Gen2EMData](channel, "EMData.GetStatus"), cache)
return c, nil return c, nil
} }

View file

@ -46,7 +46,7 @@ func NewCachedFromConfig(ctx context.Context, other map[string]any) (Plugin, err
}, nil }, nil
} }
func cachedGetter[T any](o *cachedPlugin, valuer func(ctx context.Context) (func() (T, error), error)) (func() (T, error), error) { func (o *cachedPlugin) cachedGetter[T any](valuer func(ctx context.Context) (func() (T, error), error)) (func() (T, error), error) {
value, err := valuer(o.ctx) value, err := valuer(o.ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("cached: %w", err) return nil, fmt.Errorf("cached: %w", err)
@ -77,23 +77,23 @@ func cachedGetter[T any](o *cachedPlugin, valuer func(ctx context.Context) (func
var _ StringGetter = (*cachedPlugin)(nil) var _ StringGetter = (*cachedPlugin)(nil)
func (o *cachedPlugin) StringGetter() (func() (string, error), error) { func (o *cachedPlugin) StringGetter() (func() (string, error), error) {
return cachedGetter(o, o.value.StringGetter) return o.cachedGetter(o.value.StringGetter)
} }
var _ FloatGetter = (*cachedPlugin)(nil) var _ FloatGetter = (*cachedPlugin)(nil)
func (o *cachedPlugin) FloatGetter() (func() (float64, error), error) { func (o *cachedPlugin) FloatGetter() (func() (float64, error), error) {
return cachedGetter(o, o.value.FloatGetter) return o.cachedGetter(o.value.FloatGetter)
} }
var _ IntGetter = (*cachedPlugin)(nil) var _ IntGetter = (*cachedPlugin)(nil)
func (o *cachedPlugin) IntGetter() (func() (int64, error), error) { func (o *cachedPlugin) IntGetter() (func() (int64, error), error) {
return cachedGetter(o, o.value.IntGetter) return o.cachedGetter(o.value.IntGetter)
} }
var _ BoolGetter = (*cachedPlugin)(nil) var _ BoolGetter = (*cachedPlugin)(nil)
func (o *cachedPlugin) BoolGetter() (func() (bool, error), error) { func (o *cachedPlugin) BoolGetter() (func() (bool, error), error) {
return cachedGetter(o, o.value.BoolGetter) return o.cachedGetter(o.value.BoolGetter)
} }

View file

@ -15,7 +15,7 @@ func TestCachedHitAndExpiry(t *testing.T) {
o := &cachedPlugin{ctx: context.Background(), clock: c, cache: time.Hour} o := &cachedPlugin{ctx: context.Background(), clock: c, cache: time.Hour}
var calls int var calls int
get, err := cachedGetter(o, func(context.Context) (func() (int64, error), error) { get, err := o.cachedGetter(func(context.Context) (func() (int64, error), error) {
return func() (int64, error) { return func() (int64, error) {
calls++ calls++
return int64(calls), nil return int64(calls), nil
@ -47,7 +47,7 @@ func TestCachedDoesNotCacheErrors(t *testing.T) {
o := &cachedPlugin{ctx: context.Background(), clock: c, cache: time.Hour} o := &cachedPlugin{ctx: context.Background(), clock: c, cache: time.Hour}
var calls int var calls int
get, err := cachedGetter(o, func(context.Context) (func() (int64, error), error) { get, err := o.cachedGetter(func(context.Context) (func() (int64, error), error) {
return func() (int64, error) { return func() (int64, error) {
calls++ calls++
return 0, errors.New("boom") return 0, errors.New("boom")

View file

@ -60,11 +60,8 @@ type httpTestSuite struct {
func (suite *httpTestSuite) SetupSuite() { func (suite *httpTestSuite) SetupSuite() {
suite.h = new(httpHandler) suite.h = new(httpHandler)
suite.srv = httptest.NewServer(suite.h) suite.srv = httptest.NewTestServer(suite.T(), suite.h)
} suite.srv.Start()
func (suite *httpTestSuite) TearDown() {
suite.srv.Close()
} }
func (suite *httpTestSuite) TestGet() { func (suite *httpTestSuite) TestGet() {

View file

@ -125,7 +125,7 @@ func (p *Script) StringGetter() (func() (string, error), error) {
}, nil }, nil
} }
func scriptSetter[T any](p *Script, param string) (func(T) error, error) { func (p *Script) scriptSetter[T any](param string) (func(T) error, error) {
return func(val T) error { return func(val T) error {
cmd, err := util.ReplaceFormatted(p.script, map[string]any{ cmd, err := util.ReplaceFormatted(p.script, map[string]any{
param: val, param: val,
@ -143,19 +143,19 @@ var _ IntSetter = (*Script)(nil)
// IntSetter invokes script with parameter replaced by int value // IntSetter invokes script with parameter replaced by int value
func (p *Script) IntSetter(param string) (func(int64) error, error) { func (p *Script) IntSetter(param string) (func(int64) error, error) {
return scriptSetter[int64](p, param) return p.scriptSetter[int64](param)
} }
var _ BoolSetter = (*Script)(nil) var _ BoolSetter = (*Script)(nil)
// BoolSetter invokes script with parameter replaced by bool value // BoolSetter invokes script with parameter replaced by bool value
func (p *Script) BoolSetter(param string) (func(bool) error, error) { func (p *Script) BoolSetter(param string) (func(bool) error, error) {
return scriptSetter[bool](p, param) return p.scriptSetter[bool](param)
} }
var _ StringSetter = (*Script)(nil) var _ StringSetter = (*Script)(nil)
// StringSetter returns a function that invokes a script with parameter by a string value // StringSetter returns a function that invokes a script with parameter by a string value
func (p *Script) StringSetter(param string) (func(string) error, error) { func (p *Script) StringSetter(param string) (func(string) error, error) {
return scriptSetter[string](p, param) return p.scriptSetter[string](param)
} }

View file

@ -33,8 +33,7 @@ func NewSleepFromConfig(ctx context.Context, other map[string]any) (Plugin, erro
} }
// sleeper is the generic sleeper function for sleepPlugin // sleeper is the generic sleeper function for sleepPlugin
// it is currently not possible to write this as a method func (o *sleepPlugin) sleeper[T comparable]() func(T) error {
func sleeper[T comparable](o *sleepPlugin) func(T) error {
return func(val T) error { return func(val T) error {
<-time.After(o.duration) <-time.After(o.duration)
@ -45,17 +44,17 @@ func sleeper[T comparable](o *sleepPlugin) func(T) error {
var _ IntSetter = (*sleepPlugin)(nil) var _ IntSetter = (*sleepPlugin)(nil)
func (o *sleepPlugin) IntSetter(param string) (func(int64) error, error) { func (o *sleepPlugin) IntSetter(param string) (func(int64) error, error) {
return sleeper[int64](o), nil return o.sleeper[int64](), nil
} }
var _ FloatSetter = (*sleepPlugin)(nil) var _ FloatSetter = (*sleepPlugin)(nil)
func (o *sleepPlugin) FloatSetter(param string) (func(float64) error, error) { func (o *sleepPlugin) FloatSetter(param string) (func(float64) error, error) {
return sleeper[float64](o), nil return o.sleeper[float64](), nil
} }
var _ BoolSetter = (*sleepPlugin)(nil) var _ BoolSetter = (*sleepPlugin)(nil)
func (o *sleepPlugin) BoolSetter(param string) (func(bool) error, error) { func (o *sleepPlugin) BoolSetter(param string) (func(bool) error, error) {
return sleeper[bool](o), nil return o.sleeper[bool](), nil
} }

View file

@ -16,7 +16,7 @@ func TestSockePlugin(t *testing.T) {
ctx, cancel := context.WithTimeout(t.Context(), time.Second*10) ctx, cancel := context.WithTimeout(t.Context(), time.Second*10)
defer cancel() defer cancel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c, err := websocket.Accept(w, r, nil) c, err := websocket.Accept(w, r, nil)
require.NoError(t, err) require.NoError(t, err)
defer c.Close(websocket.StatusNormalClosure, "") defer c.Close(websocket.StatusNormalClosure, "")
@ -36,8 +36,7 @@ func TestSockePlugin(t *testing.T) {
} }
} }
})) }))
srv.Start()
defer srv.Close()
addr := "ws://" + srv.Listener.Addr().String() addr := "ws://" + srv.Listener.Addr().String()
p, err := NewSocketPluginFromConfig(map[string]any{ p, err := NewSocketPluginFromConfig(map[string]any{

View file

@ -48,9 +48,7 @@ func NewValidPlugin(ctx context.Context, valid func() (bool, error), value Confi
} }
} }
var _ StringGetter = (*validPlugin)(nil) func (o *validPlugin) validGetter[T any](valuer func(ctx context.Context) (func() (T, error), error)) (func() (T, error), error) {
func validGetter[T any](o *validPlugin, valuer func(ctx context.Context) (func() (T, error), error)) (func() (T, error), error) {
value, err := valuer(o.ctx) value, err := valuer(o.ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("valid: %w", err) return nil, fmt.Errorf("valid: %w", err)
@ -74,17 +72,17 @@ func validGetter[T any](o *validPlugin, valuer func(ctx context.Context) (func()
var _ StringGetter = (*validPlugin)(nil) var _ StringGetter = (*validPlugin)(nil)
func (o *validPlugin) StringGetter() (func() (string, error), error) { func (o *validPlugin) StringGetter() (func() (string, error), error) {
return validGetter(o, o.value.StringGetter) return o.validGetter(o.value.StringGetter)
} }
var _ FloatGetter = (*validPlugin)(nil) var _ FloatGetter = (*validPlugin)(nil)
func (o *validPlugin) FloatGetter() (func() (float64, error), error) { func (o *validPlugin) FloatGetter() (func() (float64, error), error) {
return validGetter(o, o.value.FloatGetter) return o.validGetter(o.value.FloatGetter)
} }
var _ IntGetter = (*validPlugin)(nil) var _ IntGetter = (*validPlugin)(nil)
func (o *validPlugin) IntGetter() (func() (int64, error), error) { func (o *validPlugin) IntGetter() (func() (int64, error), error) {
return validGetter(o, o.value.IntGetter) return o.validGetter(o.value.IntGetter)
} }

View file

@ -76,8 +76,7 @@ type deferredState[T comparable] struct {
} }
// setter is the generic setter function for watchdogPlugin // setter is the generic setter function for watchdogPlugin
// it is currently not possible to write this as a method func (o *watchdogPlugin) setter[T comparable](set func(T) error, reset []T) func(T) error {
func setter[T comparable](o *watchdogPlugin, set func(T) error, reset []T) func(T) error {
var state *deferredState[T] var state *deferredState[T]
// seed with now, not zero: otherwise the first write's delay computes to 0 and skips // seed with now, not zero: otherwise the first write's delay computes to 0 and skips
// deferral, which is wrong for an unknown last write // deferral, which is wrong for an unknown last write
@ -195,7 +194,7 @@ func (o *watchdogPlugin) IntSetter(param string) (func(int64) error, error) {
} }
} }
res := setter(o, set, reset) res := o.setter(set, reset)
if o.initial != nil { if o.initial != nil {
val, err := strconv.ParseInt(*o.initial, 10, 64) val, err := strconv.ParseInt(*o.initial, 10, 64)
if err != nil { if err != nil {
@ -229,7 +228,7 @@ func (o *watchdogPlugin) FloatSetter(param string) (func(float64) error, error)
} }
} }
res := setter(o, set, reset) res := o.setter(set, reset)
if o.initial != nil { if o.initial != nil {
val, err := strconv.ParseFloat(*o.initial, 64) val, err := strconv.ParseFloat(*o.initial, 64)
if err != nil { if err != nil {
@ -263,7 +262,7 @@ func (o *watchdogPlugin) BoolSetter(param string) (func(bool) error, error) {
reset = append(reset, val) reset = append(reset, val)
} }
res := setter(o, set, reset) res := o.setter(set, reset)
if o.initial != nil { if o.initial != nil {
val, err := strconv.ParseBool(*o.initial) val, err := strconv.ParseBool(*o.initial)
if err != nil { if err != nil {

View file

@ -22,7 +22,7 @@ func TestWatchdogSetterConcurrency(t *testing.T) {
var u atomic.Uint32 var u atomic.Uint32
set := setter(p, func(i int) error { set := p.setter(func(i int) error {
if !u.CompareAndSwap(0, 1) { if !u.CompareAndSwap(0, 1) {
return errors.New("race") return errors.New("race")
} }
@ -63,7 +63,7 @@ func TestWatchdogDeferredUpdate(t *testing.T) {
} }
var calls []int var calls []int
set := setter(p, func(i int) error { set := p.setter(func(i int) error {
calls = append(calls, i) calls = append(calls, i)
return nil return nil
}, []int{1}) // 1 is reset value }, []int{1}) // 1 is reset value
@ -108,7 +108,7 @@ func TestWatchdogCancelPendingDeferredUpdate(t *testing.T) {
} }
var calls []int var calls []int
set := setter(p, func(i int) error { set := p.setter(func(i int) error {
calls = append(calls, i) calls = append(calls, i)
return nil return nil
}, []int{1}) // 1 is reset value }, []int{1}) // 1 is reset value
@ -154,7 +154,7 @@ func TestWatchdogDelayBackwardCompatibility(t *testing.T) {
} }
var calls []int var calls []int
set := setter(p, func(i int) error { set := p.setter(func(i int) error {
calls = append(calls, i) calls = append(calls, i)
return nil return nil
}, []int{1}) // 1 is reset value }, []int{1}) // 1 is reset value
@ -185,7 +185,7 @@ func TestWatchdogResetStopsInflightTick(t *testing.T) {
var sawReset, stale atomic.Bool var sawReset, stale atomic.Bool
set := setter(p, func(v int) error { set := p.setter(func(v int) error {
if v == 1 { if v == 1 {
sawReset.Store(true) sawReset.Store(true)
// hold the lock so an in-flight tick queues behind the reset // hold the lock so an in-flight tick queues behind the reset

View file

@ -39,9 +39,11 @@ func serveSession(w http.ResponseWriter, r *http.Request, sessions chan<- *yamux
// session to the sessions channel. Simulates the cloud proxy. // session to the sessions channel. Simulates the cloud proxy.
func tunnelTestServer(t *testing.T, sessions chan<- *yamux.Session) *httptest.Server { func tunnelTestServer(t *testing.T, sessions chan<- *yamux.Session) *httptest.Server {
t.Helper() t.Helper()
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
serveSession(w, r, sessions) serveSession(w, r, sessions)
})) }))
srv.Start()
return srv
} }
// requireReachable opens a stream through the server session and issues an // requireReachable opens a stream through the server session and issues an
@ -78,7 +80,6 @@ func TestTunnelReconnect(t *testing.T) {
sessions := make(chan *yamux.Session, 4) sessions := make(chan *yamux.Session, 4)
srv := tunnelTestServer(t, sessions) srv := tunnelTestServer(t, sessions)
defer srv.Close()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
@ -104,11 +105,11 @@ func TestTunnelReconnect(t *testing.T) {
// the proxy rejects credentials (401/403); a new token requires a restart. // the proxy rejects credentials (401/403); a new token requires a restart.
func TestTunnelRejectedCredentialsStops(t *testing.T) { func TestTunnelRejectedCredentialsStops(t *testing.T) {
var attempts atomic.Int32 var attempts atomic.Int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
attempts.Add(1) attempts.Add(1)
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
})) }))
defer srv.Close() srv.Start()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
@ -138,7 +139,7 @@ func TestTunnelReconnectsAfterTransientError(t *testing.T) {
var attempts atomic.Int32 var attempts atomic.Int32
sessions := make(chan *yamux.Session, 4) sessions := make(chan *yamux.Session, 4)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// first attempt fails transiently, later ones succeed // first attempt fails transiently, later ones succeed
if attempts.Add(1) == 1 { if attempts.Add(1) == 1 {
w.WriteHeader(http.StatusServiceUnavailable) w.WriteHeader(http.StatusServiceUnavailable)
@ -146,7 +147,7 @@ func TestTunnelReconnectsAfterTransientError(t *testing.T) {
} }
serveSession(w, r, sessions) serveSession(w, r, sessions)
})) }))
defer srv.Close() srv.Start()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")

View file

@ -23,10 +23,10 @@ func newTestConnection(baseURL string) *Connection {
// newStateConnection returns a connection serving state for any entity // newStateConnection returns a connection serving state for any entity
func newStateConnection(t *testing.T, state string) *Connection { func newStateConnection(t *testing.T, state string) *Connection {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"entity_id":"sensor.foo","state":%q}`, state) fmt.Fprintf(w, `{"entity_id":"sensor.foo","state":%q}`, state)
})) }))
t.Cleanup(srv.Close) srv.Start()
return newTestConnection(srv.URL) return newTestConnection(srv.URL)
} }
@ -116,13 +116,13 @@ func TestCallSwitchService_DomainDispatch(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
var gotPath, gotBody string var gotPath, gotBody string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotPath = r.URL.Path gotPath = r.URL.Path
body, _ := io.ReadAll(r.Body) body, _ := io.ReadAll(r.Body)
gotBody = string(body) gotBody = string(body)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
})) }))
defer srv.Close() srv.Start()
err := newTestConnection(srv.URL).CallSwitchService(tc.entity, tc.turnOn) err := newTestConnection(srv.URL).CallSwitchService(tc.entity, tc.turnOn)

View file

@ -4,19 +4,9 @@ package urlvalues
import ( import (
"errors" "errors"
"net/url" "net/url"
"slices"
"strings" "strings"
) )
// Copy creates a deep copy of url values
func Copy(q url.Values) url.Values {
res := make(url.Values, len(q))
for k, v := range q {
res[k] = slices.Clone(v)
}
return res
}
// Require verifies that url contains the required non-nil values // Require verifies that url contains the required non-nil values
func Require(q url.Values, keys ...string) error { func Require(q url.Values, keys ...string) error {
for _, k := range keys { for _, k := range keys {

View file

@ -14,10 +14,10 @@ import (
"net/url" "net/url"
"strings" "strings"
"time" "time"
"uuid"
"github.com/evcc-io/evcc/server/db/settings" "github.com/evcc-io/evcc/server/db/settings"
"github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/request"
"github.com/google/uuid"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -92,7 +92,7 @@ func (v *Identity) loginCCI(password string) (*oauth2.Token, error) {
// fetch RSA cert, signin, exchange the auth code for CCI and then CCS tokens // fetch RSA cert, signin, exchange the auth code for CCI and then CCS tokens
func (v *Identity) loginCCIPassword(password string) (*oauth2.Token, error) { func (v *Identity) loginCCIPassword(password string) (*oauth2.Token, error) {
c := v.config.CCI c := v.config.CCI
deviceID := uuid.NewString() deviceID := uuid.New().String()
v.log.DEBUG.Println("cci: logging in via OneApp/CCI password login") v.log.DEBUG.Println("cci: logging in via OneApp/CCI password login")

View file

@ -9,12 +9,12 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"uuid"
"github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/oauth" "github.com/evcc-io/evcc/util/oauth"
"github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/request"
"github.com/google/uuid"
"github.com/samber/lo" "github.com/samber/lo"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -79,11 +79,11 @@ func (v *Identity) getDeviceID() (string, error) {
return "", err return "", err
} }
uuid := uuid.NewString() id := uuid.New().String()
data := map[string]any{ data := map[string]any{
"pushRegId": lo.RandomString(64, []rune("0123456789ABCDEF")), "pushRegId": lo.RandomString(64, []rune("0123456789ABCDEF")),
"pushType": v.config.PushType, "pushType": v.config.PushType,
"uuid": uuid, "uuid": id,
} }
headers := map[string]string{ headers := map[string]string{

View file

@ -150,8 +150,8 @@ func TestLoginUsesCCIAndWiresRefreshCCI(t *testing.T) {
loginSrv := httptest.NewServer(loginMux) loginSrv := httptest.NewServer(loginMux)
defer loginSrv.Close() defer loginSrv.Close()
var refreshCalls int32 var refreshCalls atomic.Int32
var exchangeCalls int32 var exchangeCalls atomic.Int32
cciMux := http.NewServeMux() cciMux := http.NewServeMux()
cciMux.HandleFunc("/domain/api/v1/auth/token", func(w http.ResponseWriter, _ *http.Request) { cciMux.HandleFunc("/domain/api/v1/auth/token", func(w http.ResponseWriter, _ *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{ _ = json.NewEncoder(w).Encode(map[string]any{
@ -165,14 +165,14 @@ func TestLoginUsesCCIAndWiresRefreshCCI(t *testing.T) {
// the first (login) exchange returns a token expiring inside oauth2's // the first (login) exchange returns a token expiring inside oauth2's
// 10s buffer, so the next Token() call triggers exactly one refresh // 10s buffer, so the next Token() call triggers exactly one refresh
resp := map[string]any{"accessToken": "ccs-token-2", "expiresTime": time.Now().Add(time.Hour).Unix()} resp := map[string]any{"accessToken": "ccs-token-2", "expiresTime": time.Now().Add(time.Hour).Unix()}
if atomic.AddInt32(&exchangeCalls, 1) == 1 { if exchangeCalls.Add(1) == 1 {
resp["accessToken"] = "ccs-token-1" resp["accessToken"] = "ccs-token-1"
resp["expiresTime"] = time.Now().Add(5 * time.Second).Unix() resp["expiresTime"] = time.Now().Add(5 * time.Second).Unix()
} }
_ = json.NewEncoder(w).Encode(resp) _ = json.NewEncoder(w).Encode(resp)
}) })
cciMux.HandleFunc("/domain/api/v2/auth/token-refresh", func(w http.ResponseWriter, _ *http.Request) { cciMux.HandleFunc("/domain/api/v2/auth/token-refresh", func(w http.ResponseWriter, _ *http.Request) {
atomic.AddInt32(&refreshCalls, 1) refreshCalls.Add(1)
_ = json.NewEncoder(w).Encode(map[string]any{ _ = json.NewEncoder(w).Encode(map[string]any{
"accessToken": "cci-access-2", "refreshToken": "cci-refresh-2", "accessToken": "cci-access-2", "refreshToken": "cci-refresh-2",
}) })
@ -192,12 +192,12 @@ func TestLoginUsesCCIAndWiresRefreshCCI(t *testing.T) {
token, err := identity.Token() token, err := identity.Token()
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "ccs-token-2", token.AccessToken) assert.Equal(t, "ccs-token-2", token.AccessToken)
assert.EqualValues(t, 1, atomic.LoadInt32(&refreshCalls), "expected TokenSource to be wired to refreshCCI") assert.EqualValues(t, 1, refreshCalls.Load(), "expected TokenSource to be wired to refreshCCI")
// the now long-lived token must not trigger a further refresh // the now long-lived token must not trigger a further refresh
_, err = identity.Token() _, err = identity.Token()
require.NoError(t, err) require.NoError(t, err)
assert.EqualValues(t, 1, atomic.LoadInt32(&refreshCalls), "unexpected additional refresh") assert.EqualValues(t, 1, refreshCalls.Load(), "unexpected additional refresh")
} }
// TestLoginPropagatesLegacyError covers the legacy path: a failure there must // TestLoginPropagatesLegacyError covers the legacy path: a failure there must

View file

@ -5,12 +5,12 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"time" "time"
"uuid"
"github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/vehicle/jlr" "github.com/evcc-io/evcc/vehicle/jlr"
"github.com/google/uuid"
) )
// https://github.com/ardevd/jlrpy // https://github.com/ardevd/jlrpy
@ -54,7 +54,7 @@ func NewJLRFromConfig(other map[string]any) (api.Vehicle, error) {
log := util.NewLogger("jlr").Redact(cc.User, cc.Password, cc.VIN, cc.DeviceID) log := util.NewLogger("jlr").Redact(cc.User, cc.Password, cc.VIN, cc.DeviceID)
if cc.DeviceID == "" { if cc.DeviceID == "" {
cc.DeviceID = uuid.NewString() cc.DeviceID = uuid.New().String()
log.WARN.Println("new device id generated, add `deviceid` to config:", cc.DeviceID) log.WARN.Println("new device id generated, add `deviceid` to config:", cc.DeviceID)
} }

View file

@ -4,8 +4,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"sync" "sync"
"uuid"
"github.com/google/uuid"
) )
// Helper provides utility primitives // Helper provides utility primitives

View file

@ -7,13 +7,13 @@ import (
"net/url" "net/url"
"strings" "strings"
"sync" "sync"
"uuid"
"github.com/evcc-io/evcc/server/db/settings" "github.com/evcc-io/evcc/server/db/settings"
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/oauth" "github.com/evcc-io/evcc/util/oauth"
"github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/transport" "github.com/evcc-io/evcc/util/transport"
"github.com/google/uuid"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )

View file

@ -8,12 +8,12 @@ import (
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"uuid"
"github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/vehicle/polestar/pb" "github.com/evcc-io/evcc/vehicle/polestar/pb"
"github.com/google/uuid"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
@ -119,7 +119,7 @@ func (v *GrpcAPI) Battery(ctx context.Context, vin string) (*pb.Battery, error)
ctx = metadata.AppendToOutgoingContext(ctx, "vin", vin) ctx = metadata.AppendToOutgoingContext(ctx, "vin", vin)
res, err := v.client.GetLatestBattery(ctx, &pb.GetBatteryRequest{ res, err := v.client.GetLatestBattery(ctx, &pb.GetBatteryRequest{
Id: uuid.NewString(), Id: uuid.New().String(),
Vin: vin, Vin: vin,
}) })
if err != nil { if err != nil {

View file

@ -95,7 +95,7 @@ func (v *API) doRepeatedRequest(path string, event_id string) error {
event_id) event_id)
var res requests.Answer[requests.ChargeStatus] var res requests.Answer[requests.ChargeStatus]
if _, err = doRequest(v, req, &res); err == nil { if _, err = v.doRequest(req, &res); err == nil {
v.store(res.Data) v.store(res.Data)
} }
return err return err
@ -120,7 +120,7 @@ func (v *API) repeatRequest(path string, event_id string) {
v.mu.Unlock() v.mu.Unlock()
} }
func doRequest[T any](v *API, req *http.Request, result *requests.Answer[T]) (string, error) { func (v *API) doRequest[T any](req *http.Request, result *requests.Answer[T]) (string, error) {
resp, err := v.Do(req) resp, err := v.Do(req)
if err != nil { if err != nil {
return "", err return "", err
@ -183,7 +183,7 @@ func (v *API) Wakeup(vin string) error {
return err return err
} }
doRequest[any](v, req, nil) v.doRequest[any](req, nil)
return nil return nil
} }
@ -214,7 +214,7 @@ func (v *API) Status(vin string) (requests.ChargeStatus, error) {
"") "")
var res requests.Answer[requests.ChargeStatus] var res requests.Answer[requests.ChargeStatus]
event_id, err := doRequest(v, req, &res) event_id, err := v.doRequest(req, &res)
if err != nil { if err != nil {
return zero, err return zero, err
} }
@ -234,7 +234,7 @@ func (v *API) Status(vin string) (requests.ChargeStatus, error) {
event_id) event_id)
// answer not yet available, keep polling in the background // answer not yet available, keep polling in the background
if _, err = doRequest(v, req, &res); err == api.ErrMustRetry { if _, err = v.doRequest(req, &res); err == api.ErrMustRetry {
v.mu.Lock() v.mu.Lock()
v.state = stateRunning v.state = stateRunning
v.mu.Unlock() v.mu.Unlock()

View file

@ -16,7 +16,7 @@ import (
func TestCommandResponse(t *testing.T) { func TestCommandResponse(t *testing.T) {
sponsor.Subject = "any" sponsor.Subject = "any"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL.Path) fmt.Println(r.URL.Path)
switch r.URL.Path { switch r.URL.Path {
@ -29,7 +29,7 @@ func TestCommandResponse(t *testing.T) {
w.Write([]byte(`{"response": null, "error": "vehicle unavailable: vehicle is offline or asleep"}`)) w.Write([]byte(`{"response": null, "error": "vehicle unavailable: vehicle is offline or asleep"}`))
} }
})) }))
defer srv.Close() srv.Start()
ts := oauth2.StaticTokenSource(new(oauth2.Token)) ts := oauth2.StaticTokenSource(new(oauth2.Token))
client, err := tesla.NewClient(t.Context(), tesla.WithTokenSource(ts)) client, err := tesla.NewClient(t.Context(), tesla.WithTokenSource(ts))

View file

@ -9,15 +9,14 @@ import (
"net/http/cookiejar" "net/http/cookiejar"
"net/url" "net/url"
"strings" "strings"
"uuid"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/urlvalues"
"github.com/evcc-io/evcc/vehicle/vag" "github.com/evcc-io/evcc/vehicle/vag"
"github.com/google/uuid"
"github.com/samber/lo" "github.com/samber/lo"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
) )
@ -42,7 +41,7 @@ func LoginWithAuthURL(log *util.Logger, uri string, q url.Values, user, password
var verify func(url.Values) var verify func(url.Values)
// add code challenge // add code challenge
q = urlvalues.Copy(q) q = q.Clone()
if rt := q.Get("response_type"); strings.Contains(rt, "code") { if rt := q.Get("response_type"); strings.Contains(rt, "code") {
verify = vag.ChallengeAndVerifier(q) verify = vag.ChallengeAndVerifier(q)
} }
@ -92,7 +91,7 @@ func (v *Service) Login(uri, user, password string) (url.Values, error) {
// add nonce and state // add nonce and state
query := url.Values{ query := url.Values{
"nonce": {lo.RandomString(43, lo.LettersCharset)}, "nonce": {lo.RandomString(43, lo.LettersCharset)},
"state": {uuid.NewString()}, "state": {uuid.New().String()},
} }
uri = uri + "&" + query.Encode() uri = uri + "&" + query.Encode()