feat(platform): add vehicle service API
This commit is contained in:
@@ -29,6 +29,7 @@ func (h *Handler) routes() {
|
||||
h.mux.HandleFunc("GET /api/vehicles", h.handleVehicles)
|
||||
h.mux.HandleFunc("GET /api/vehicles/resolve", h.handleVehicleResolve)
|
||||
h.mux.HandleFunc("GET /api/vehicles/coverage", h.handleVehicleCoverage)
|
||||
h.mux.HandleFunc("GET /api/vehicle-service", h.handleVehicleDetail)
|
||||
h.mux.HandleFunc("GET /api/vehicles/detail", h.handleVehicleDetail)
|
||||
h.mux.HandleFunc("GET /api/realtime/vehicles", h.handleVehicleRealtime)
|
||||
h.mux.HandleFunc("GET /api/realtime/locations", h.handleRealtimeLocations)
|
||||
|
||||
@@ -64,6 +64,30 @@ func TestHandlerVehicleDetail(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerVehicleServiceCanonicalEndpoint(t *testing.T) {
|
||||
handler := NewHandler(NewService(NewMockStore()))
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/vehicle-service?keyword=粤AG18312&protocol=JT808", nil)
|
||||
handler.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var body struct {
|
||||
Data VehicleDetail `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("response JSON should decode: %v body=%s", err, rec.Body.String())
|
||||
}
|
||||
if body.Data.Resolution == nil || body.Data.Resolution.VIN != "LB9A32A24R0LS1426" {
|
||||
t.Fatalf("vehicle service should resolve canonical vehicle identity, got %+v body=%s", body.Data.Resolution, rec.Body.String())
|
||||
}
|
||||
for _, row := range body.Data.Realtime {
|
||||
if row.Protocol != "JT808" {
|
||||
t.Fatalf("vehicle service should keep source filter, got realtime row %+v body=%s", row, rec.Body.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerVehicleDetailResolvesPlateToVIN(t *testing.T) {
|
||||
handler := NewHandler(NewService(NewMockStore()))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -74,7 +74,7 @@ export const api = {
|
||||
vehicles: (params = new URLSearchParams()) => request<Page<VehicleRow>>(`/api/vehicles?${params.toString()}`),
|
||||
vehicleResolve: (params = new URLSearchParams()) => request<VehicleIdentityResolution>(`/api/vehicles/resolve?${params.toString()}`),
|
||||
vehicleCoverage: (params = new URLSearchParams()) => request<Page<VehicleCoverageRow>>(`/api/vehicles/coverage?${params.toString()}`),
|
||||
vehicleDetail: (params = new URLSearchParams()) => request<VehicleDetail>(`/api/vehicles/detail?${params.toString()}`),
|
||||
vehicleDetail: (params = new URLSearchParams()) => request<VehicleDetail>(`/api/vehicle-service?${params.toString()}`),
|
||||
vehicleRealtime: (params = new URLSearchParams()) => request<Page<VehicleRealtimeRow>>(`/api/realtime/vehicles?${params.toString()}`),
|
||||
realtimeLocations: (params = new URLSearchParams()) => request<Page<RealtimeLocationRow>>(`/api/realtime/locations?${params.toString()}`),
|
||||
historyLocations: (params = new URLSearchParams()) => request<Page<HistoryLocationRow>>(`/api/history/locations?${params.toString()}`),
|
||||
|
||||
@@ -249,7 +249,7 @@ test('switches vehicle detail to a source from the coverage cards', async () =>
|
||||
window.history.replaceState(null, '', '/#/detail?keyword=VIN001');
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||
const path = String(input);
|
||||
if (path.includes('/api/vehicles/detail')) {
|
||||
if (path.includes('/api/vehicle-service')) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
@@ -320,14 +320,14 @@ test('switches vehicle detail to a source from the coverage cards', async () =>
|
||||
await waitFor(() => {
|
||||
expect(window.location.hash).toBe('#/detail?keyword=VIN001&protocol=JT808');
|
||||
});
|
||||
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/vehicles/detail?keyword=VIN001&protocol=JT808'), undefined);
|
||||
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/vehicle-service?keyword=VIN001&protocol=JT808'), undefined);
|
||||
});
|
||||
|
||||
test('opens history with the currently selected vehicle detail source', async () => {
|
||||
window.history.replaceState(null, '', '/#/detail?keyword=VIN001');
|
||||
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||
const path = String(input);
|
||||
if (path.includes('/api/vehicles/detail')) {
|
||||
if (path.includes('/api/vehicle-service')) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
@@ -446,9 +446,9 @@ test('opens vehicle service from an empty history query with the current filters
|
||||
|
||||
test('shows selected source scope on vehicle detail', async () => {
|
||||
window.history.replaceState(null, '', '/#/detail?keyword=VIN001&protocol=JT808');
|
||||
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
||||
const path = String(input);
|
||||
if (path.includes('/api/vehicles/detail')) {
|
||||
if (path.includes('/api/vehicle-service')) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
@@ -516,4 +516,5 @@ test('shows selected source scope on vehicle detail', async () => {
|
||||
|
||||
expect(await screen.findByText('当前查看范围')).toBeInTheDocument();
|
||||
expect(screen.getByText('单一来源:JT808')).toBeInTheDocument();
|
||||
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/vehicle-service?keyword=VIN001&protocol=JT808'), undefined);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user