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.
148 lines
5.1 KiB
Go
148 lines
5.1 KiB
Go
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")
|
|
}
|
|
}
|