178 lines
6.3 KiB
Go
178 lines
6.3 KiB
Go
package stats
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
)
|
|
|
|
func TestMetricRepositoryQueriesDailyMetricsWithFilters(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("sqlmock.New() error = %v", err)
|
|
}
|
|
defer db.Close()
|
|
mock.ExpectQuery("SELECT vin, stat_date, protocol, daily_mileage_km, first_total_mileage_km, latest_total_mileage_km, sample_count, updated_at FROM vehicle_daily_mileage").
|
|
WithArgs("LKLG7C4E3NA774736", "JT808", "2026-07-01", "2026-07-01", 20, 0).
|
|
WillReturnRows(sqlmock.NewRows([]string{
|
|
"vin", "stat_date", "protocol", "daily_mileage_km",
|
|
"first_total_mileage_km", "latest_total_mileage_km", "sample_count",
|
|
"updated_at",
|
|
}).AddRow(
|
|
"LKLG7C4E3NA774736", time.Date(2026, 7, 1, 0, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)), "JT808", 12.3,
|
|
12345.6, 12357.9, 13, time.Date(2026, 7, 1, 23, 9, 36, 0, time.FixedZone("Asia/Shanghai", 8*3600)),
|
|
))
|
|
|
|
repository := NewMetricRepository(db)
|
|
rows, err := repository.Query(context.Background(), MetricQuery{
|
|
VIN: "LKLG7C4E3NA774736",
|
|
Protocol: "JT808",
|
|
DateFrom: "2026-07-01",
|
|
DateTo: "2026-07-01",
|
|
Limit: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Query() error = %v", err)
|
|
}
|
|
if len(rows) != 1 {
|
|
t.Fatalf("row count = %d", len(rows))
|
|
}
|
|
if rows[0].DailyMileageKM != 12.3 {
|
|
t.Fatalf("unexpected row: %#v", rows[0])
|
|
}
|
|
if rows[0].StatDate != "2026-07-01" || rows[0].UpdatedAt != "2026-07-01 23:09:36" {
|
|
t.Fatalf("unexpected time formatting: %#v", rows[0])
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("sql expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMetricHandlerReturnsDailyMetrics(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("sqlmock.New() error = %v", err)
|
|
}
|
|
defer db.Close()
|
|
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM vehicle_daily_mileage").
|
|
WithArgs("LB9A32A21R0LS1707", "GB32960", "2020-07-01", "2020-07-01").
|
|
WillReturnRows(sqlmock.NewRows([]string{"total"}).AddRow(42))
|
|
mock.ExpectQuery("SELECT vin, stat_date, protocol, daily_mileage_km, first_total_mileage_km, latest_total_mileage_km, sample_count, updated_at FROM vehicle_daily_mileage").
|
|
WithArgs("LB9A32A21R0LS1707", "GB32960", "2020-07-01", "2020-07-01", 50, 0).
|
|
WillReturnRows(sqlmock.NewRows([]string{
|
|
"vin", "stat_date", "protocol", "daily_mileage_km",
|
|
"first_total_mileage_km", "latest_total_mileage_km", "sample_count",
|
|
"updated_at",
|
|
}).AddRow(
|
|
"LB9A32A21R0LS1707", "2020-07-01", "GB32960", 0.0,
|
|
53490.9, 53490.9, 3, "2026-07-01 22:28:25",
|
|
))
|
|
|
|
handler := NewMetricHandler(NewMetricRepository(db))
|
|
request := httptest.NewRequest(http.MethodGet, "/api/stats/daily-metrics?vin=LB9A32A21R0LS1707&protocol=GB32960&dateFrom=2020-07-01&dateTo=2020-07-01&includeTotal=true", nil)
|
|
response := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("status = %d body=%s", response.Code, response.Body.String())
|
|
}
|
|
body := response.Body.String()
|
|
for _, want := range []string{`"vin":"LB9A32A21R0LS1707"`, `"daily_mileage_km":0`, `"total":42`} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("response missing %s: %s", want, body)
|
|
}
|
|
}
|
|
if strings.Contains(body, "created_at") {
|
|
t.Fatalf("daily mileage response should not expose created_at: %s", body)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("sql expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMetricHandlerReturnsEmptyItemsArrayWhenNoRows(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("sqlmock.New() error = %v", err)
|
|
}
|
|
defer db.Close()
|
|
mock.ExpectQuery("SELECT vin, stat_date, protocol, daily_mileage_km, first_total_mileage_km, latest_total_mileage_km, sample_count, updated_at FROM vehicle_daily_mileage").
|
|
WithArgs("YUTONG_MQTT", 50, 0).
|
|
WillReturnRows(sqlmock.NewRows([]string{
|
|
"vin", "stat_date", "protocol", "daily_mileage_km",
|
|
"first_total_mileage_km", "latest_total_mileage_km", "sample_count",
|
|
"updated_at",
|
|
}))
|
|
|
|
handler := NewMetricHandler(NewMetricRepository(db))
|
|
request := httptest.NewRequest(http.MethodGet, "/api/stats/daily-metrics?protocol=YUTONG_MQTT", nil)
|
|
response := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("status = %d body=%s", response.Code, response.Body.String())
|
|
}
|
|
body := response.Body.String()
|
|
if !strings.Contains(body, `"items":[]`) || strings.Contains(body, `"items":null`) {
|
|
t.Fatalf("expected empty items array, got: %s", body)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("sql expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMetricHandlerSkipsTotalCountByDefault(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("sqlmock.New() error = %v", err)
|
|
}
|
|
defer db.Close()
|
|
mock.ExpectQuery("SELECT vin, stat_date, protocol, daily_mileage_km, first_total_mileage_km, latest_total_mileage_km, sample_count, updated_at FROM vehicle_daily_mileage").
|
|
WithArgs("JT808", 1, 0).
|
|
WillReturnRows(sqlmock.NewRows([]string{
|
|
"vin", "stat_date", "protocol", "daily_mileage_km",
|
|
"first_total_mileage_km", "latest_total_mileage_km", "sample_count",
|
|
"updated_at",
|
|
}).AddRow(
|
|
"LKLG7C4E3NA774736", "2026-07-02", "JT808", 12.3,
|
|
8792.8, 8805.1, 30, "2026-07-02 23:59:59",
|
|
))
|
|
|
|
handler := NewMetricHandler(NewMetricRepository(db))
|
|
request := httptest.NewRequest(http.MethodGet, "/api/stats/daily-metrics?protocol=JT808&limit=1", nil)
|
|
response := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("status = %d body=%s", response.Code, response.Body.String())
|
|
}
|
|
body := response.Body.String()
|
|
if !strings.Contains(body, `"total":1`) {
|
|
t.Fatalf("response should use page size as total when total count is not requested: %s", body)
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Fatalf("sql expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMetricHandlerRejectsInvalidPagination(t *testing.T) {
|
|
handler := NewMetricHandler(NewMetricRepository(&sql.DB{}))
|
|
request := httptest.NewRequest(http.MethodGet, "/api/stats/daily-metrics?limit=2001", nil)
|
|
response := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if response.Code != http.StatusBadRequest {
|
|
t.Fatalf("status = %d body=%s", response.Code, response.Body.String())
|
|
}
|
|
}
|