feat: sync vehicle monitoring and accumulated platform updates

Add monitoring metrics, numeric sorting, report-time display, vehicle number labels, and map layout fixes. Include accumulated mileage, hydrogen, OneOS authentication, open platform, ingestion, deployment, and UI updates.

Exclude temporary files, generated reports, and credentials. Validate frontend 632 tests and production build, API packages, vehicle-map tests, and release installer tests. Gateway suite has one pre-existing date-sensitive identity throttling test failure reproduced on the parent commit.
This commit is contained in:
lingniu
2026-10-26 18:05:59 +08:00
parent 8b32ae2988
commit d6b957dfc8
290 changed files with 28238 additions and 1647 deletions
@@ -0,0 +1,147 @@
package gzev
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestDecimalPreservesNullAndZero(t *testing.T) {
for _, tc := range []struct {
in string
valid bool
value string
}{{"null", false, ""}, {`"0.0"`, true, "0.0"}, {"203.125", true, "203.125"}} {
var d Decimal
if err := json.Unmarshal([]byte(tc.in), &d); err != nil || d.Valid != tc.valid || d.Value != tc.value {
t.Fatalf("%s: %+v %v", tc.in, d, err)
}
}
for _, bad := range []string{`""`, `"NaN"`, `-1`, `"1.2345"`, `"100000000000"`} {
var d Decimal
if json.Unmarshal([]byte(bad), &d) == nil {
t.Fatalf("accepted %s", bad)
}
}
}
func TestDateShanghaiBoundary(t *testing.T) {
now := time.Date(2026, 9, 27, 16, 0, 0, 0, time.UTC)
if ValidateDate("2026-09-27", now) != nil || ValidateDate("2026-09-28", now) == nil {
t.Fatal("Shanghai day cutoff incorrect")
}
}
func TestFetchPaginationAndCookie(t *testing.T) {
calls := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
if r.Header.Get("Authorization") != "Bearer fake" {
t.Error("missing bearer")
}
cookie, e := r.Cookie("token")
if e != nil || cookie.Value != "fake" {
t.Error("missing token cookie")
}
if r.URL.Query().Get("queryBeginDate") != "2024-01-01" || r.URL.Query().Get("queryEndDate") != "2024-01-01" || r.URL.Query().Get("queryType") != "2" {
t.Error("wrong daily query")
}
fmt.Fprintf(w, `{"code":"0","data":{"pageNum":%d,"total":2,"list":[{"vin":"LNXNEGRR7SR31945%d","dateRange":"2024-01-01 - 2024-01-01","runMileage":"12.1","hyRunMileage":null,"chargeRunMileage":"0"}]}}`, calls, calls)
}))
defer srv.Close()
c := NewClient(Session{Authorization: "Bearer fake", Cookies: []SessionCookie{{"token", "fake"}}})
c.baseURL = srv.URL
c.PageSize = 1
c.Delay = 0
day, err := c.FetchDay(context.Background(), "2024-01-01")
if err != nil || len(day.Rows) != 2 || len(day.Pages) != 2 || calls != 2 {
t.Fatalf("pagination: %+v %v", day, err)
}
if day.Rows[0].Hydrogen.Valid || !day.Rows[0].Charge.Valid {
t.Fatal("null lost")
}
}
func TestRejectsIncompleteSource(t *testing.T) {
for name, body := range map[string]string{
"missing code": `{"data":{}}`,
"missing total": `{"code":"0","data":{"pageNum":1,"list":[]}}`,
"empty page": `{"code":"0","data":{"pageNum":1,"total":2,"list":[]}}`,
"wrong date": `{"code":"0","data":{"pageNum":1,"total":1,"list":[{"vin":"LNXNEGRR7SR319456","dateRange":"2024-01-01 - 2024-01-02"}]}}`,
} {
t.Run(name, func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, body) }))
defer srv.Close()
c := NewClient(Session{})
c.baseURL = srv.URL
if _, err := c.FetchDay(context.Background(), "2024-01-01"); err == nil {
t.Fatal("accepted incomplete day")
}
})
}
}
func TestAuthAndPermissionAreDifferent(t *testing.T) {
for _, tc := range []struct {
code string
auth bool
}{{"401", true}, {"00000004", false}, {"403", false}} {
calls := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls++; fmt.Fprintf(w, `{"code":%q}`, tc.code) }))
c := NewClient(Session{})
c.baseURL = srv.URL
_, err := c.FetchDay(context.Background(), "2024-01-01")
srv.Close()
if err == nil || errors.Is(err, ErrAuth) != tc.auth || calls != 1 {
t.Fatalf("code %s: %v calls=%d", tc.code, err, calls)
}
}
}
func TestNoCredentialRedirect(t *testing.T) {
hit := false
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { hit = true }))
defer target.Close()
source := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, target.URL, 302) }))
defer source.Close()
c := NewClient(Session{Authorization: "Bearer fake"})
c.baseURL = source.URL
_, err := c.FetchDay(context.Background(), "2024-01-01")
if hit || !errors.Is(err, ErrAuth) {
t.Fatalf("redirect leaked credentials: %v", err)
}
}
func TestReadSessionPermissionsAndCookieAllowlist(t *testing.T) {
p := filepath.Join(t.TempDir(), "session.json")
body := `{"authorization":"Bearer fake","cookies":[{"name":"token","value":"fake"}]}`
os.WriteFile(p, []byte(body), 0644)
if _, err := ReadSession(p); err == nil {
t.Fatal("accepted world-readable session")
}
os.Chmod(p, 0600)
if _, err := ReadSession(p); err != nil {
t.Fatal(err)
}
os.WriteFile(p, []byte(strings.ReplaceAll(body, `"name":"token"`, `"name":"password"`)), 0600)
if _, err := ReadSession(p); err == nil {
t.Fatal("accepted password cookie")
}
}
func TestVerifyAccountRejectsOtherLogin(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/evdata-sso/api/base/getUserInfo" || r.URL.Query().Get("token") != "fake" {
t.Error("incorrect identity contract")
}
fmt.Fprint(w, `{"code":"0","data":{"loginname":"different-account"}}`)
}))
defer srv.Close()
c := NewClient(Session{Authorization: "Bearer fake"})
c.baseURL = srv.URL
if c.VerifyAccount(context.Background(), "guangdonglingniu") == nil {
t.Fatal("mixed account data")
}
}