From 152eb2c7b72db4aceaf159120971f66d360bb40a Mon Sep 17 00:00:00 2001 From: Alex Savin Date: Sun, 6 Jul 2025 14:23:35 -0400 Subject: [PATCH] Refactor code structure and remove redundant sections for improved readability and maintainability --- README.md | 45 ++++- client.go | 32 ++-- client_test.go | 328 +++++++++++++++++++++++++++++++++++++ consts.go | 10 +- example/config.sample.yaml | 2 +- example/example.go | 107 +++++++----- utils_test.go | 22 +-- vehicle.go | 158 +++++++++--------- vehicle_test.go | 265 ++++++++++++++++++++++++++++++ 9 files changed, 813 insertions(+), 156 deletions(-) create mode 100644 client_test.go create mode 100644 vehicle_test.go diff --git a/README.md b/README.md index cca03cd..0c49ac4 100644 --- a/README.md +++ b/README.md @@ -20,16 +20,47 @@ The following samples will assist you to become as comfortable as possible with import "git.savin.nyc/alex/mysubaru" ``` -#### Create a new MySubaru connection and get a car by VIN +#### Create a new MySubaru API Client ```go // Create a MySubaru Client -mysubaru, _ := New() -outback := mysubaru.GetVehicleByVIN("VIN-CODE-HERE") +msc, err := mysubaru.New(cfg) +if err != nil { + cfg.Logger.Error("cannot create MySubaru client", "error", err) + os.Exit(1) +} +``` + +#### Get a car by VIN +```go +outback, err := msc.GetVehicleByVIN("1HGCM82633A004352") +if err != nil { + cfg.Logger.Error("cannot get a vehicle by VIN", "error", err) + os.Exit(1) +} ``` #### Start/Stop Lights request ```go -outback.LightsStart() -time.Sleep(30 * time.Second) -outback.LightsStop() -``` \ No newline at end of file +// Execute a LightsStart command +events, err := outback.LightsStart() +if err != nil { + cfg.Logger.Error("cannot execute LightsStart command", "error", err) + os.Exit(1) +} +for event := range events { + fmt.Printf("Lights Start Event: %+v\n", event) +} + +// Wait for a while to see the lights on +time.Sleep(20 * time.Second) + +// Execute a LightsStop command +events, err = outback.LightsStop() +if err != nil { + cfg.Logger.Error("cannot execute LightsStop command", "error", err) + os.Exit(1) +} +for event := range events { + fmt.Printf("Lights Stop Event: %+v\n", event) +} +``` diff --git a/client.go b/client.go index 6a22e41..e904cc5 100644 --- a/client.go +++ b/client.go @@ -83,7 +83,7 @@ func New(config *config.Config) (*Client, error) { } client.currentVin = client.listOfVins[0] } else { - client.logger.Error("there no cars assigned to the account") + client.logger.Error("there are no cars assigned to the account") return nil, err } return client, nil @@ -94,19 +94,21 @@ func (c *Client) SelectVehicle(vin string) (*VehicleData, error) { if vin == "" { vin = c.currentVin } - vinCheck(vin) params := map[string]string{ "vin": vin, "_": timestamp()} reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"] - // TODO: Add error handling - resp, _ := c.execute(GET, reqURL, params, false) + resp, err := c.execute(GET, reqURL, params, false) + if err != nil { + c.logger.Error("error while executing SelectVehicle request", "request", "SelectVehicle", "error", err.Error()) + return nil, errors.New("error while executing SelectVehicle request: " + err.Error()) + } // c.logger.Debug("http request output", "request", "SelectVehicle", "body", resp) var vd VehicleData - err := json.Unmarshal(resp.Data, &vd) + err = json.Unmarshal(resp.Data, &vd) if err != nil { c.logger.Error("error while parsing json", "request", "SelectVehicle", "error", err.Error()) return nil, errors.New("error while parsing json while vehicle selection") @@ -116,32 +118,36 @@ func (c *Client) SelectVehicle(vin string) (*VehicleData, error) { } // GetVehicles . -func (c *Client) GetVehicles() []*Vehicle { +func (c *Client) GetVehicles() ([]*Vehicle, error) { var vehicles []*Vehicle for _, vin := range c.listOfVins { - vehicle, err := c.GetVehicleByVIN(vin) + vehicle, err := c.GetVehicleByVin(vin) if err != nil { - c.logger.Error("cannot get vehile data", "request", "SelectVehicle", "error", err.Error()) + c.logger.Error("cannot get vehicle data", "request", "GetVehicles", "error", err.Error()) + return nil, errors.New("cannot get vehicle data: " + err.Error()) } vehicles = append(vehicles, vehicle) } - return vehicles + return vehicles, nil } // GetVehicleByVIN . -func (c *Client) GetVehicleByVIN(vin string) (*Vehicle, error) { +func (c *Client) GetVehicleByVin(vin string) (*Vehicle, error) { var vehicle *Vehicle if slices.Contains(c.listOfVins, vin) { params := map[string]string{ "vin": vin, "_": timestamp()} reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"] - // TODO: Add error handling - resp, _ := c.execute(GET, reqURL, params, false) + resp, err := c.execute(GET, reqURL, params, false) + if err != nil { + c.logger.Error("error while executing GetVehicleByVIN request", "request", "GetVehicleByVIN", "error", err.Error()) + return nil, errors.New("error while executing GetVehicleByVIN request: " + err.Error()) + } // c.logger.Debug("http request output", "request", "GetVehicleByVIN", "body", resp) var vd VehicleData - err := json.Unmarshal(resp.Data, &vd) + err = json.Unmarshal(resp.Data, &vd) if err != nil { c.logger.Error("error while parsing json", "request", "GetVehicleByVIN", "error", err.Error()) } diff --git a/client_test.go b/client_test.go new file mode 100644 index 0000000..ce0eb67 --- /dev/null +++ b/client_test.go @@ -0,0 +1,328 @@ +package mysubaru + +import ( + "fmt" + "log/slog" + "net" + "net/http" + "net/http/httptest" + "testing" + + "git.savin.nyc/alex/mysubaru/config" +) + +func makeConfig(t *testing.T) *config.Config { + return &config.Config{ + MySubaru: config.MySubaru{ + Credentials: config.Credentials{ + Username: "user", + Password: "pass", + DeviceID: "dev123", + DeviceName: "devname", + }, + Region: "TEST", + AutoReconnect: true, + }, + TimeZone: "America/New_York", + // Logger: slogt.New(t), + Logger: slog.New(slog.NewTextHandler(nil, nil)), + } +} + +func mockMySubaruApi(t *testing.T, handler http.HandlerFunc) *httptest.Server { + + // Create a listener with the desired port + l, err := net.Listen("tcp", "127.0.0.1:56765") + if err != nil { + t.Fatalf("failed to create listener: %v", err) + } + + ts := httptest.NewUnstartedServer(handler) + // http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // // Handle API_LOGIN endpoint + // if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_LOGIN"] && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","enableXtime":true,"termsAndConditionsAccepted":true,"digitalGlobeConnectId":"0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeImageTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeTransparentTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","tomtomKey":"DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`) + // } + // // Handle API_VALIDATE_SESSION endpoint + // if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_VALIDATE_SESSION"] && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"success":true,"data":{"vhsId":14662115789,"odometerValue":31694,"odometerValueKilometers":50996,"eventDate":1751742945000,"eventDateStr":"2025-07-05T19:15+0000","eventDateCarUser":1751742945000,"eventDateStrCarUser":"2025-07-05T19:15+0000","latitude":40.700153,"longitude":-74.401405,"positionHeadingDegree":"154","tirePressureFrontLeft":"2482","tirePressureFrontRight":"2482","tirePressureRearLeft":"2413","tirePressureRearRight":"2482","tirePressureFrontLeftPsi":"36","tirePressureFrontRightPsi":"36","tirePressureRearLeftPsi":"35","tirePressureRearRightPsi":"36","doorBootPosition":"CLOSED","doorEngineHoodPosition":"CLOSED","doorFrontLeftPosition":"CLOSED","doorFrontRightPosition":"CLOSED","doorRearLeftPosition":"CLOSED","doorRearRightPosition":"CLOSED","doorBootLockStatus":"LOCKED","doorFrontLeftLockStatus":"LOCKED","doorFrontRightLockStatus":"LOCKED","doorRearLeftLockStatus":"LOCKED","doorRearRightLockStatus":"LOCKED","distanceToEmptyFuelMiles":259.73,"distanceToEmptyFuelKilometers":418,"avgFuelConsumptionMpg":102.2,"avgFuelConsumptionLitersPer100Kilometers":2.3,"evStateOfChargePercent":null,"evDistanceToEmptyMiles":null,"evDistanceToEmptyKilometers":null,"evDistanceToEmptyByStateMiles":null,"evDistanceToEmptyByStateKilometers":null,"vehicleStateType":"IGNITION_OFF","windowFrontLeftStatus":"CLOSE","windowFrontRightStatus":"CLOSE","windowRearLeftStatus":"CLOSE","windowRearRightStatus":"CLOSE","windowSunroofStatus":"CLOSE","tyreStatusFrontLeft":"UNKNOWN","tyreStatusFrontRight":"UNKNOWN","tyreStatusRearLeft":"UNKNOWN","tyreStatusRearRight":"UNKNOWN","remainingFuelPercent":90,"distanceToEmptyFuelMiles10s":260,"distanceToEmptyFuelKilometers10s":420}}`) + // } + // // Handle API_LOCATE endpoint + // if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LOCATE"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LOCATE"], "g2")) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"id": 1, "name": "John Doe"}`) + // } + // // Handle API_LOCK endpoint + // if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LOCK"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LOCK"], "g2")) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"remoteServiceStatus","data":{"serviceRequestId":"1HGCM82633A004352_1751747301812_20_@NGTP","success":false,"cancelled":false,"remoteServiceType":"lock","remoteServiceState":"started","subState":null,"errorCode":null,"result":null,"updateTime":null,"vin":"1HGCM82633A004352","errorDescription":null}}`) + // } + // // Handle API_LOCK_CANCEL endpoint + // if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LOCK_CANCEL"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LOCK_CANCEL"], "g2")) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"id": 1, "name": "John Doe"}`) + // } + // // Handle API_UNLOCK endpoint + // if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_UNLOCK"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_UNLOCK"], "g2")) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"remoteServiceStatus","data":{"serviceRequestId":"1HGCM82633A004352_1751747271262_19_@NGTP","success":false,"cancelled":false,"remoteServiceType":"unlock","remoteServiceState":"started","subState":null,"errorCode":null,"result":null,"updateTime":null,"vin":"1HGCM82633A004352","errorDescription":null}}`) + // } + // // Handle API_UNLOCK_CANCEL endpoint + // if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_UNLOCK_CANCEL"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_UNLOCK_CANCEL"], "g2")) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"id": 1, "name": "John Doe"}`) + // } + // // Handle API_HORN_LIGHTS endpoint + // if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_HORN_LIGHTS"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_HORN_LIGHTS"], "g2")) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"remoteServiceStatus","data":{"serviceRequestId":"1HGCM82633A004352_1751746568969_47_@NGTP","success":true,"cancelled":false,"remoteServiceType":"hornLights","remoteServiceState":"started","subState":null,"errorCode":null,"result":null,"updateTime":1751746569000,"vin":"1HGCM82633A004352","errorDescription":null}}`) + // } + // // Handle API_HORN_LIGHTS_CANCEL endpoint + // if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_HORN_LIGHTS_CANCEL"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_HORN_LIGHTS_CANCEL"], "g2")) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"remoteServiceStatus","data":{"serviceRequestId":"1HGCM82633A004352_1751746568969_47_@NGTP","success":true,"cancelled":false,"remoteServiceType":"hornLights","remoteServiceState":"cancelling","subState":null,"errorCode":null,"result":null,"updateTime":1751746569000,"vin":"1HGCM82633A004352","errorDescription":null}}`) + // } + // // Handle API_HORN_LIGHTS_STOP endpoint + // if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_HORN_LIGHTS_STOP"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_HORN_LIGHTS_STOP"], "g2")) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"remoteServiceStatus","data":{"serviceRequestId":"1HGCM82633A004352_1751746568969_47_@NGTP","success":true,"cancelled":false,"remoteServiceType":"hornLights","remoteServiceState":"stopping","subState":null,"errorCode":null,"result":null,"updateTime":1751746569000,"vin":"1HGCM82633A004352","errorDescription":null}}`) + // } + // // Handle API_LIGHTS endpoint + // if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LIGHTS"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LIGHTS"], "g2")) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"remoteServiceStatus","data":{"serviceRequestId":"1HGCM82633A004352_1751746568969_47_@NGTP","success":true,"cancelled":false,"remoteServiceType":"lightsOnly","remoteServiceState":"started","subState":null,"errorCode":null,"result":null,"updateTime":1751746569000,"vin":"1HGCM82633A004352","errorDescription":null}}`) + // } + // // Handle API_LIGHTS_CANCEL endpoint + // if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LIGHTS_CANCEL"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LIGHTS_CANCEL"], "g2")) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"remoteServiceStatus","data":{"serviceRequestId":"1HGCM82633A004352_1751746568969_47_@NGTP","success":true,"cancelled":false,"remoteServiceType":"lightsOnly","remoteServiceState":"stopping","subState":null,"errorCode":null,"result":null,"updateTime":1751746569000,"vin":"1HGCM82633A004352","errorDescription":null}}`) + // } + // // Handle API_LIGHTS_STOP endpoint + // if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LIGHTS_STOP"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_LIGHTS_STOP"], "g2")) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"remoteServiceStatus","data":{"serviceRequestId":"1HGCM82633A004352_1751746568969_47_@NGTP","success":true,"cancelled":false,"remoteServiceType":"lightsOnly","remoteServiceState":"finished","subState":null,"errorCode":null,"result":null,"updateTime":1751746569000,"vin":"1HGCM82633A004352","errorDescription":null}}`) + // } + // // Handle API_G2_REMOTE_ENGINE_START endpoint + // if (r.URL.Path == MOBILE_API_VERSION+apiURLs["API_G2_REMOTE_ENGINE_START"]) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"id": 1, "name": "John Doe"}`) + // } + // // Handle API_G2_REMOTE_ENGINE_START_CANCEL endpoint + // if (r.URL.Path == MOBILE_API_VERSION+apiURLs["API_G2_REMOTE_ENGINE_START_CANCEL"]) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // fmt.Fprint(w, `{"id": 1, "name": "John Doe"}`) + // } + // // Handle API_G2_REMOTE_ENGINE_STOP endpoint + // if (r.URL.Path == MOBILE_API_VERSION+apiURLs["API_G2_REMOTE_ENGINE_STOP"]) && r.Method == http.MethodPost { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // json.NewEncoder(w).Encode( + // Response{ + // Success: true, + // DataName: "remoteServiceStatus", + // Data: []byte(`{"serviceRequestId":"1HGCM82633A004352_1751745367457_47_@NGTP","success":true,"cancelled":false,"remoteServiceType":"lightsOnly","remoteServiceState":"started","subState":null,"errorCode":null,"result":null,"updateTime":1751745367000,"vin":"1HGCM82633A004352","errorDescription":null}`), + // }, + // ) + // } + // // Handle API_REMOTE_SVC_STATUS endpoint + // if (r.URL.Path == MOBILE_API_VERSION+apiURLs["API_REMOTE_SVC_STATUS"]) && r.Method == http.MethodGet { + // w.Header().Set("Content-Type", "application/json") + // w.WriteHeader(http.StatusOK) + // json.NewEncoder(w).Encode( + // Response{ + // Success: true, + // DataName: "remoteServiceStatus", + // Data: []byte(`{"remoteServiceState":"finished"}`), + // }, + // ) + // } + + // Close the default listener created by NewUnstartedServer and replace it + // with our custom listener. + ts.Listener.Close() + ts.Listener = l + + return ts +} + +func TestNew_Success(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { + // Handle API_LOGIN endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_LOGIN"] && r.Method == http.MethodPost { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","enableXtime":true,"termsAndConditionsAccepted":true,"digitalGlobeConnectId":"0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeImageTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeTransparentTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","tomtomKey":"DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`) + } + } + + ts := mockMySubaruApi(t, handler) + ts.Start() + defer ts.Close() + + cfg := makeConfig(t) + + msc, err := New(cfg) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if msc == nil { + t.Fatalf("expected MySubaru API client, got nil") + } + if !msc.isAuthenticated || !msc.isRegistered { + t.Errorf("expected authenticated and registered true, got %v %v", msc.isAuthenticated, msc.isRegistered) + } + if msc.currentVin != "1HGCM82633A004352" { + t.Errorf("expected currentVin 1HGCM82633A004352, got %v", msc.currentVin) + } +} + +// func TestNew_Failure(t *testing.T) { +// ts := mockMySubaruApi(t) +// ts.Start() +// defer ts.Close() // Ensure the server is closed after the test + +// cfg := makeConfig(t) +// cfg.MySubaru.Credentials.Username = "" // Invalid username + +// client, err := New(cfg) +// if err == nil { +// t.Fatalf("expected error, got nil") +// } +// if client != nil { +// t.Fatalf("expected nil client, got %v", client) +// } +// if client.isAuthenticated || client.isRegistered { +// t.Errorf("expected authenticated and registered false, got %v %v", client.isAuthenticated, client.isRegistered) +// } +// if client.currentVin != "" { +// t.Errorf("expected currentVin empty, got %v", client.currentVin) +// } +// } + +func TestSelectVehicle_Success(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { + // Handle API_LOGIN endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_LOGIN"] && r.Method == http.MethodPost { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","enableXtime":true,"termsAndConditionsAccepted":true,"digitalGlobeConnectId":"0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeImageTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeTransparentTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","tomtomKey":"DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`) + } + // Handle SELECT_VEHICLE endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"vehicle","data":{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":"2023","modelCode":"PDL","engineSize":2.4,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"8KV8","licensePlateState":"NJ","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":["REMOTE","SAFETY","Retail3"],"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":["ABS_MIL","ACCS","AHBL_MIL","ATF_MIL","AWD_MIL","BSD","BSDRCT_MIL","CEL_MIL","CP1_5HHU","EBD_MIL","EOL_MIL","EPAS_MIL","EPB_MIL","ESS_MIL","EYESIGHT","ISS_MIL","MOONSTAT","OPL_MIL","PANPM-TUIRWAOC","PWAAADWWAP","RAB_MIL","RCC","REARBRK","RES","RESCC","RES_HVAC_HFS","RES_HVAC_VFS","RHSF","RPOI","RPOIA","RTGU","RVFS","SRH_MIL","SRS_MIL","SXM360L","T23DCM","TEL_MIL","TIF_35","TIR_33","TLD","TPMS_MIL","VALET","VDC_MIL","WASH_MIL","WDWSTAT","g3"],"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":"ACTIVE","authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":"Outback","subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":"Cosmic Blue Pearl","sunsetUpgraded":true,"intDescrip":"Black","transCode":"CVT","provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}}`) + } + } + ts := mockMySubaruApi(t, handler) + ts.Start() + defer ts.Close() + + cfg := makeConfig(t) + + msc, err := New(cfg) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + vehicle, err := msc.SelectVehicle("1HGCM82633A004352") + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if vehicle == nil { + t.Fatalf("expected vehicle, got nil") + } + if vehicle.Vin != "1HGCM82633A004352" { + t.Errorf("expected vehicle VIN 1HGCM82633A004352, got %v", vehicle.Vin) + } +} + +func TestGetVehicleByVin_Success(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { + // Handle API_LOGIN endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_LOGIN"] && r.Method == http.MethodPost { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","enableXtime":true,"termsAndConditionsAccepted":true,"digitalGlobeConnectId":"0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeImageTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeTransparentTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","tomtomKey":"DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`) + } + // Handle SELECT_VEHICLE endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"vehicle","data":{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":"2023","modelCode":"PDL","engineSize":2.4,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"8KV8","licensePlateState":"NJ","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":["REMOTE","SAFETY","Retail3"],"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":["ABS_MIL","ACCS","AHBL_MIL","ATF_MIL","AWD_MIL","BSD","BSDRCT_MIL","CEL_MIL","CP1_5HHU","EBD_MIL","EOL_MIL","EPAS_MIL","EPB_MIL","ESS_MIL","EYESIGHT","ISS_MIL","MOONSTAT","OPL_MIL","PANPM-TUIRWAOC","PWAAADWWAP","RAB_MIL","RCC","REARBRK","RES","RESCC","RES_HVAC_HFS","RES_HVAC_VFS","RHSF","RPOI","RPOIA","RTGU","RVFS","SRH_MIL","SRS_MIL","SXM360L","T23DCM","TEL_MIL","TIF_35","TIR_33","TLD","TPMS_MIL","VALET","VDC_MIL","WASH_MIL","WDWSTAT","g3"],"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":"ACTIVE","authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":"Outback","subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":"Cosmic Blue Pearl","sunsetUpgraded":true,"intDescrip":"Black","transCode":"CVT","provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}}`) + } + // Handle API_VEHICLE_HEALTH endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_VEHICLE_HEALTH"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":{"lastUpdatedDate":1751742945000,"vehicleHealthItems":[{"warningCode":10,"b2cCode":"airbag","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"SRS_MIL"},{"warningCode":4,"b2cCode":"oilTemp","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"ATF_MIL"},{"warningCode":39,"b2cCode":"blindspot","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"BSDRCT_MIL"},{"warningCode":2,"b2cCode":"engineFail","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"CEL_MIL"},{"warningCode":44,"b2cCode":"pkgBrake","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"EPB_MIL"},{"warningCode":8,"b2cCode":"ebd","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"EBD_MIL"},{"warningCode":3,"b2cCode":"oilWarning","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"EOL_MIL"},{"warningCode":1,"b2cCode":"washer","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"WASH_MIL"},{"warningCode":50,"b2cCode":"iss","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"ISS_MIL"},{"warningCode":53,"b2cCode":"oilPres","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"OPL_MIL"},{"warningCode":11,"b2cCode":"epas","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"EPAS_MIL"},{"warningCode":69,"b2cCode":"revBrake","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"RAB_MIL"},{"warningCode":14,"b2cCode":"telematics","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"TEL_MIL"},{"warningCode":9,"b2cCode":"tpms","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"TPMS_MIL"},{"warningCode":7,"b2cCode":"vdc","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"VDC_MIL"},{"warningCode":6,"b2cCode":"abs","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"ABS_MIL"},{"warningCode":5,"b2cCode":"awd","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"AWD_MIL"},{"warningCode":12,"b2cCode":"eyesight","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"ESS_MIL"},{"warningCode":30,"b2cCode":"ahbl","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"AHBL_MIL"},{"warningCode":31,"b2cCode":"srh","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"SRH_MIL"}]}}`) + } + // Handle API_VEHICLE_STATUS endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_VEHICLE_STATUS"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":{"vhsId":14662115789,"odometerValue":31694,"odometerValueKilometers":50996,"eventDate":1751742945000,"eventDateStr":"2025-07-05T19:15+0000","eventDateCarUser":1751742945000,"eventDateStrCarUser":"2025-07-05T19:15+0000","latitude":40.700153,"longitude":-74.401405,"positionHeadingDegree":"154","tirePressureFrontLeft":"2482","tirePressureFrontRight":"2482","tirePressureRearLeft":"2413","tirePressureRearRight":"2482","tirePressureFrontLeftPsi":"36","tirePressureFrontRightPsi":"36","tirePressureRearLeftPsi":"35","tirePressureRearRightPsi":"36","doorBootPosition":"CLOSED","doorEngineHoodPosition":"CLOSED","doorFrontLeftPosition":"CLOSED","doorFrontRightPosition":"CLOSED","doorRearLeftPosition":"CLOSED","doorRearRightPosition":"CLOSED","doorBootLockStatus":"LOCKED","doorFrontLeftLockStatus":"LOCKED","doorFrontRightLockStatus":"LOCKED","doorRearLeftLockStatus":"LOCKED","doorRearRightLockStatus":"LOCKED","distanceToEmptyFuelMiles":259.73,"distanceToEmptyFuelKilometers":418,"avgFuelConsumptionMpg":102.2,"avgFuelConsumptionLitersPer100Kilometers":2.3,"evStateOfChargePercent":null,"evDistanceToEmptyMiles":null,"evDistanceToEmptyKilometers":null,"evDistanceToEmptyByStateMiles":null,"evDistanceToEmptyByStateKilometers":null,"vehicleStateType":"IGNITION_OFF","windowFrontLeftStatus":"CLOSE","windowFrontRightStatus":"CLOSE","windowRearLeftStatus":"CLOSE","windowRearRightStatus":"CLOSE","windowSunroofStatus":"CLOSE","tyreStatusFrontLeft":"UNKNOWN","tyreStatusFrontRight":"UNKNOWN","tyreStatusRearLeft":"UNKNOWN","tyreStatusRearRight":"UNKNOWN","remainingFuelPercent":90,"distanceToEmptyFuelMiles10s":260,"distanceToEmptyFuelKilometers10s":420}}`) + } + // Handle API_CONDITION endpoint + if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_CONDITION"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_CONDITION"], "g2")) && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"remoteServiceStatus","data":{"serviceRequestId":null,"success":true,"cancelled":false,"remoteServiceType":"condition","remoteServiceState":"finished","subState":null,"errorCode":null,"result":{"avgFuelConsumption":null,"avgFuelConsumptionUnit":"MPG","distanceToEmptyFuel":null,"distanceToEmptyFuelUnit":"MILES","odometer":31692,"odometerUnit":"MILES","tirePressureFrontLeft":null,"tirePressureFrontLeftUnit":"PSI","tirePressureFrontRight":null,"tirePressureFrontRightUnit":"PSI","tirePressureRearLeft":null,"tirePressureRearLeftUnit":"PSI","tirePressureRearRight":null,"tirePressureRearRightUnit":"PSI","lastUpdatedTime":"2025-07-05T19:15:45.000+0000","windowFrontLeftStatus":"CLOSE","windowFrontRightStatus":"CLOSE","windowRearLeftStatus":"CLOSE","windowRearRightStatus":"CLOSE","windowSunroofStatus":"CLOSE","remainingFuelPercent":"90","evDistanceToEmpty":null,"evDistanceToEmptyUnit":null,"evChargerStateType":null,"evIsPluggedIn":null,"evStateOfChargeMode":null,"evTimeToFullyCharged":null,"evStateOfChargePercent":null,"vehicleStateType":"IGNITION_OFF","doorBootLockStatus":"LOCKED","doorBootPosition":"CLOSED","doorEngineHoodPosition":"CLOSED","doorFrontLeftLockStatus":"LOCKED","doorFrontLeftPosition":"CLOSED","doorFrontRightLockStatus":"LOCKED","doorFrontRightPosition":"CLOSED","doorRearLeftLockStatus":"LOCKED","doorRearLeftPosition":"CLOSED","doorRearRightLockStatus":"LOCKED","doorRearRightPosition":"CLOSED"},"updateTime":null,"vin":"1HGCM82633A004352","errorDescription":null}}`) + } + // Handle API_G2_FETCH_RES_SUBARU_PRESETS endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_G2_FETCH_RES_SUBARU_PRESETS"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":["{\"name\": \"Auto\", \"runTimeMinutes\": \"10\", \"climateZoneFrontTemp\": \"74\", \"climateZoneFrontAirMode\": \"AUTO\", \"climateZoneFrontAirVolume\": \"AUTO\", \"outerAirCirculation\": \"auto\", \"heatedRearWindowActive\": \"false\", \"airConditionOn\": \"false\", \"heatedSeatFrontLeft\": \"off\", \"heatedSeatFrontRight\": \"off\", \"startConfiguration\": \"START_ENGINE_ALLOW_KEY_IN_IGNITION\", \"canEdit\": \"true\", \"disabled\": \"false\", \"vehicleType\": \"gas\", \"presetType\": \"subaruPreset\" }","{\"name\":\"Full Cool\",\"runTimeMinutes\":\"10\",\"climateZoneFrontTemp\":\"60\",\"climateZoneFrontAirMode\":\"feet_face_balanced\",\"climateZoneFrontAirVolume\":\"7\",\"airConditionOn\":\"true\",\"heatedSeatFrontLeft\":\"high_cool\",\"heatedSeatFrontRight\":\"high_cool\",\"heatedRearWindowActive\":\"false\",\"outerAirCirculation\":\"outsideAir\",\"startConfiguration\":\"START_ENGINE_ALLOW_KEY_IN_IGNITION\",\"canEdit\":\"true\",\"disabled\":\"true\",\"vehicleType\":\"gas\",\"presetType\":\"subaruPreset\"}","{\"name\": \"Full Heat\", \"runTimeMinutes\": \"10\", \"climateZoneFrontTemp\": \"85\", \"climateZoneFrontAirMode\": \"feet_window\", \"climateZoneFrontAirVolume\": \"7\", \"airConditionOn\": \"false\", \"heatedSeatFrontLeft\": \"high_heat\", \"heatedSeatFrontRight\": \"high_heat\", \"heatedRearWindowActive\": \"true\", \"outerAirCirculation\": \"outsideAir\", \"startConfiguration\": \"START_ENGINE_ALLOW_KEY_IN_IGNITION\", \"canEdit\": \"true\", \"disabled\": \"true\", \"vehicleType\": \"gas\", \"presetType\": \"subaruPreset\" }","{\"name\": \"Full Cool\", \"runTimeMinutes\": \"10\", \"climateZoneFrontTemp\": \"60\", \"climateZoneFrontAirMode\": \"feet_face_balanced\", \"climateZoneFrontAirVolume\": \"7\", \"airConditionOn\": \"true\", \"heatedSeatFrontLeft\": \"OFF\", \"heatedSeatFrontRight\": \"OFF\", \"heatedRearWindowActive\": \"false\", \"outerAirCirculation\": \"outsideAir\", \"startConfiguration\": \"START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION\", \"canEdit\": \"true\", \"disabled\": \"true\", \"vehicleType\": \"phev\", \"presetType\": \"subaruPreset\" }","{\"name\": \"Full Heat\", \"runTimeMinutes\": \"10\", \"climateZoneFrontTemp\": \"85\", \"climateZoneFrontAirMode\": \"feet_window\", \"climateZoneFrontAirVolume\": \"7\", \"airConditionOn\": \"false\", \"heatedSeatFrontLeft\": \"high_heat\", \"heatedSeatFrontRight\": \"high_heat\", \"heatedRearWindowActive\": \"true\", \"outerAirCirculation\": \"outsideAir\", \"startConfiguration\": \"START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION\", \"canEdit\": \"true\", \"disabled\": \"true\", \"vehicleType\": \"phev\", \"presetType\": \"subaruPreset\" }"]}`) + } + // Handle API_G2_FETCH_RES_USER_PRESETS endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_G2_FETCH_RES_USER_PRESETS"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":"[{\"name\":\"Cooling\",\"runTimeMinutes\":\"10\",\"climateZoneFrontTemp\":\"65\",\"climateZoneFrontAirMode\":\"FEET_FACE_BALANCED\",\"climateZoneFrontAirVolume\":\"7\",\"outerAirCirculation\":\"outsideAir\",\"heatedRearWindowActive\":\"false\",\"heatedSeatFrontLeft\":\"HIGH_COOL\",\"heatedSeatFrontRight\":\"HIGH_COOL\",\"airConditionOn\":\"false\",\"canEdit\":\"true\",\"disabled\":\"false\",\"presetType\":\"userPreset\",\"startConfiguration\":\"START_ENGINE_ALLOW_KEY_IN_IGNITION\"}]"}`) + } + // Handle API_G2_FETCH_RES_QUICK_START_SETTINGS endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_G2_FETCH_RES_QUICK_START_SETTINGS"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":"{\"name\":\"Cooling\",\"runTimeMinutes\":\"10\",\"climateZoneFrontTemp\":\"65\",\"climateZoneFrontAirMode\":\"FEET_FACE_BALANCED\",\"climateZoneFrontAirVolume\":\"7\",\"outerAirCirculation\":\"outsideAir\",\"heatedRearWindowActive\":\"false\",\"heatedSeatFrontLeft\":\"HIGH_COOL\",\"airConditionOn\":\"false\",\"canEdit\":\"true\",\"disabled\":\"false\",\"presetType\":\"userPreset\",\"startConfiguration\":\"START_ENGINE_ALLOW_KEY_IN_IGNITION\"}"}`) + } + } + ts := mockMySubaruApi(t, handler) + ts.Start() + defer ts.Close() + + cfg := makeConfig(t) + + msc, err := New(cfg) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + vehicle, err := msc.GetVehicleByVin("1HGCM82633A004352") + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if vehicle == nil { + t.Fatalf("expected vehicle, got nil") + } + if vehicle.Vin != "1HGCM82633A004352" { + t.Errorf("expected vehicle VIN 1HGCM82633A004352, got %v", vehicle.Vin) + } +} diff --git a/consts.go b/consts.go index 246cfd8..54af365 100644 --- a/consts.go +++ b/consts.go @@ -30,12 +30,12 @@ var apiURLs = map[string]string{ "API_2FA_AUTH_VERIFY": "/twoStepAuthVerify.json", "API_LOGIN": "/login.json", // Same API for g1 and g2 "API_REFRESH_VEHICLES": "/refreshVehicles.json", - "API_SELECT_VEHICLE": "/selectVehicle.json", - "API_VALIDATE_SESSION": "/validateSession.json", - "API_VEHICLE_STATUS": "/vehicleStatus.json", + "API_SELECT_VEHICLE": "/selectVehicle.json", // Covered by test + "API_VALIDATE_SESSION": "/validateSession.json", // Covered by test + "API_VEHICLE_STATUS": "/vehicleStatus.json", // Covered by test "API_AUTHORIZE_DEVICE": "/authenticateDevice.json", "API_NAME_DEVICE": "/nameThisDevice.json", - "API_VEHICLE_HEALTH": "/vehicleHealth.json", + "API_VEHICLE_HEALTH": "/vehicleHealth.json", // Covered by test "API_CONDITION": "/service/api_gen/condition/execute.json", // Similar API for g1 and g2 -- controller should replace 'api_gen' with either 'g1' or 'g2' "API_LOCATE": "/service/api_gen/locate/execute.json", // Get the last location the vehicle has reported to Subaru "API_LOCK": "/service/api_gen/lock/execute.json", @@ -48,12 +48,12 @@ var apiURLs = map[string]string{ "API_LIGHTS": "/service/api_gen/lightsOnly/execute.json", "API_LIGHTS_CANCEL": "/service/api_gen/lightsOnly/cancel.json", "API_LIGHTS_STOP": "/service/api_gen/lightsOnly/stop.json", - "API_REMOTE_SVC_STATUS": "/service/g2/remoteService/status.json", "API_G1_LOCATE_UPDATE": "/service/g1/vehicleLocate/execute.json", // Different API for g1 and g2 "API_G1_LOCATE_STATUS": "/service/g1/vehicleLocate/status.json", "API_G1_HORN_LIGHTS_STATUS": "/service/g1/hornLights/status.json", // g1-Only API "API_G2_LOCATE_UPDATE": "/service/g2/vehicleStatus/execute.json", "API_G2_LOCATE_STATUS": "/service/g2/vehicleStatus/locationStatus.json", + "API_REMOTE_SVC_STATUS": "/service/g2/remoteService/status.json", "API_G2_SEND_POI": "/service/g2/sendPoi/execute.json", // g2-Only API "API_G2_SPEEDFENCE": "/service/g2/speedFence/execute.json", "API_G2_GEOFENCE": "/service/g2/geoFence/execute.json", diff --git a/example/config.sample.yaml b/example/config.sample.yaml index 8a7a63b..1260605 100644 --- a/example/config.sample.yaml +++ b/example/config.sample.yaml @@ -10,5 +10,5 @@ mysubaru: timezone: "America/New_York" logging: level: INFO - output: json + output: JSON source: false diff --git a/example/example.go b/example/example.go index 8add41c..b59ebeb 100644 --- a/example/example.go +++ b/example/example.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os" + "time" "git.savin.nyc/alex/mysubaru" "git.savin.nyc/alex/mysubaru/config" @@ -21,9 +22,13 @@ func main() { if err != nil { log.Fatal(err) } - cfg.Logger.Debug("printing config", "config", cfg) + // cfg.Logger.Debug("printing config", "config", cfg) - ms, _ := mysubaru.New(cfg) + msc, err := mysubaru.New(cfg) + if err != nil { + cfg.Logger.Error("cannot create MySubaru client", "error", err) + os.Exit(1) + } // subaru := ms.SelectVehicle("4S4BTGPD0P3199198") @@ -37,48 +42,66 @@ func main() { // fmt.Printf("GEN #1: %+v\n\n", subaru1.getAPIGen()) // ABS_MIL ACCS AHBL_MIL ATF_MIL AWD_MIL BSD BSDRCT_MIL CEL_MIL EBD_MIL EOL_MIL EPAS_MIL EPB_MIL ESS_MIL EYESIGHT ISS_MIL OPL_MIL PANPM-TUIRWAOC PWAAADWWAP RAB_MIL RCC REARBRK RES RESCC RHSF RPOI RPOIA RTGU RVFS SRH_MIL SRS_MIL TEL_MIL TIF_35 TIR_33 TLD TPMS_MIL VALET VDC_MIL WASH_MIL g3 - outback := ms.GetVehicleByVIN("4S4BTGPD0P3199198") - // subaru.GetLocation(true) + outback, err := msc.GetVehicleByVIN("4S4BTGPD0P3199198") + if err != nil { + cfg.Logger.Error("cannot get a vehicle by VIN", "error", err) + os.Exit(1) + } + fmt.Printf("SUBARU #1 (Vehicle Status): %s\n", outback) - // subaru.EngineStart() - fmt.Printf("SUBARU #1 (Vehicle Status):\n") - // subaru.GetVehicleStatus() - // fmt.Printf("SUBARU #1 (Vehicle Condition):\n") - // subaru.GetVehicleCondition() - // fmt.Printf("SUBARU #1: %+v\n", subaru) - // subaru.GetClimatePresets() - // subaru.GetClimateUserPresets() - // fmt.Printf("SUBARU #2: %+v\n", subaru) - // subaru.GetVehicleHealth() - // subaru.GetFeaturesList() - // subaru := mysubaru.GetVehicles()[0] + // // Execute a LightsStart command + // events, err := outback.LightsStart() + // if err != nil { + // cfg.Logger.Error("cannot execute LightsStart command", "error", err) + // os.Exit(1) + // } + // for event := range events { + // fmt.Printf("Lights Start Event: %+v\n", event) + // } - // fmt.Printf("SUBARU: %+v\n", subaru) - - // fmt.Printf("Subaru Gen: %+v\n\n", subaru.getAPIGen()) - - // subaru.EngineOn() - // subaru.GetLocation(false) - - // subaru.GetVehicleStatus() - // subaru.GetVehicleCondition() - - // subaru.GetClimateQuickPresets() - // subaru.GetClimatePresets() - // subaru.GetClimateUserPresets() - - // subaru.GetVehicleStatus() - - // subaru.GetClimateSettings() - - // subaru.EngineStart() - // time.Sleep(15 * time.Second) - // subaru.EngineStop() - - // subaru.Unlock() + // // Wait for a while to see the lights on // time.Sleep(20 * time.Second) - // subaru.Lock() - // subaru.GetLocation() - fmt.Printf("SUBARU: %+v\n", outback) + // // Execute a LightsStop command + // events, err = outback.LightsStop() + // if err != nil { + // cfg.Logger.Error("cannot execute LightsStop command", "error", err) + // os.Exit(1) + // } + // for event := range events { + // fmt.Printf("Lights Stop Event: %+v\n", event) + // } + + // Execute a Unlock command + events, err := outback.Unlock() + if err != nil { + cfg.Logger.Error("cannot execute Unlock command", "error", err) + os.Exit(1) + } + for event := range events { + fmt.Printf("Unlock Event: %+v\n", event) + } + + // Wait for a while to see the lights on + time.Sleep(20 * time.Second) + + // Execute a Lock command + events, err = outback.Lock() + if err != nil { + cfg.Logger.Error("cannot execute Lock command", "error", err) + os.Exit(1) + } + for event := range events { + fmt.Printf("Lock Event: %+v\n", event) + } + + // Execute a forced GetLocation command + // events, err = outback.GetLocation(true) + // if err != nil { + // cfg.Logger.Error("cannot execute forced GetLocation command", "error", err) + // os.Exit(1) + // } + // for event := range events { + // fmt.Printf("GeoLocation Event: %+v\n", event) + // } } diff --git a/utils_test.go b/utils_test.go index 707e8f5..d0a6812 100644 --- a/utils_test.go +++ b/utils_test.go @@ -22,6 +22,17 @@ func TestTimestamp(t *testing.T) { } } +func TestTimestamp_Format(t *testing.T) { + ts := timestamp() + matched, err := regexp.MatchString(`^\d+$`, ts) + if err != nil { + t.Fatalf("regexp error: %v", err) + } + if !matched { + t.Errorf("timestamp() = %q, want only digits", ts) + } +} + func TestUrlToGen(t *testing.T) { tests := []struct { url, gen, want string @@ -108,14 +119,3 @@ func TestUrlToGen_NoApiGen(t *testing.T) { t.Errorf("urlToGen(%q, %q) = %q, want %q", url, gen, got, url) } } - -func TestTimestamp_Format(t *testing.T) { - ts := timestamp() - matched, err := regexp.MatchString(`^\d+$`, ts) - if err != nil { - t.Fatalf("regexp error: %v", err) - } - if !matched { - t.Errorf("timestamp() = %q, want only digits", ts) - } -} \ No newline at end of file diff --git a/vehicle.go b/vehicle.go index d25d9fa..277579a 100644 --- a/vehicle.go +++ b/vehicle.go @@ -481,81 +481,6 @@ func (v *Vehicle) GetLocation(force bool) (chan string, error) { return ch, nil } -// executeServiceRequest -func (v *Vehicle) executeServiceRequest(params map[string]string, reqUrl, pollingUrl string, ch chan string, attempt int) error { - var maxAttempts = 15 - - if attempt >= maxAttempts { - v.client.logger.Error("maximum attempts reached for service request", "request", reqUrl, "attempts", attempt) - ch <- "error" - return errors.New("maximum attempts reached for service request") - } - - if v.Vin != v.client.currentVin { - v.selectVehicle() - } - - var resp *Response - var err error - if attempt == 1 { - resp, err = v.client.execute(POST, reqUrl, params, true) - if err != nil { - v.client.logger.Error("error while executing service request", "request", reqUrl, "error", err.Error()) - ch <- "error" - return err - } - } else { - resp, err = v.client.execute(GET, pollingUrl, params, false) - if err != nil { - v.client.logger.Error("error while executing service request status polling", "request", reqUrl, "error", err.Error()) - ch <- "error" - return err - } - } - - // dataName field has the list of the states [ remoteServiceStatus | errorResponse ] - if resp.DataName == "remoteServiceStatus" { - if sr, ok := v.parseServiceRequest([]byte(resp.Data)); ok { - ch <- sr.RemoteServiceState - switch sr.RemoteServiceState { - - case "finished": - // Finished RemoteServiceState Service Request does not include Service Request ID - v.client.logger.Debug("Remote service request completed successfully") - - case "started": - time.Sleep(5 * time.Second) - v.client.logger.Debug("Subaru API reports remote service request (started) is in progress", "id", sr.ServiceRequestID) - v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1) - - case "stopping": - time.Sleep(5 * time.Second) - v.client.logger.Debug("Subaru API reports remote service request (stopping) is in progress", "id", sr.ServiceRequestID) - v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1) - - default: - v.client.logger.Debug("Subaru API reports remote service request (default)") - v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1) - } - return nil - } - v.client.logger.Error("error while parsing service request json", "request", reqUrl, "response", resp.Data) - return errors.New("error while parsing service request json") - } - return errors.New("response is not a service request") -} - -// parseServiceRequest . -func (v *Vehicle) parseServiceRequest(b []byte) (ServiceRequest, bool) { - var sr ServiceRequest - err := json.Unmarshal(b, &sr) - if err != nil { - v.client.logger.Error("error while parsing service request json", "error", err.Error()) - return sr, false - } - return sr, true -} - // GetClimatePresets connects to the MySubaru API to download available climate presets. // It first attempts to establish a connection with the MySubaru API. // If successful and climate presets are found for the user's vehicle, @@ -710,11 +635,15 @@ func (v *Vehicle) GetVehicleStatus() error { v.selectVehicle() } reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_VEHICLE_STATUS"], v.getAPIGen()) - resp, _ := v.client.execute(GET, reqUrl, map[string]string{}, false) + resp, err := v.client.execute(GET, reqUrl, map[string]string{}, false) + if err != nil { + v.client.logger.Error("error while executing GetVehicleStatus request", "request", "GetVehicleStatus", "error", err.Error()) + return err + } // v.client.logger.Info("http request output", "request", "GetVehicleStatus", "body", resp) var vs VehicleStatus - err := json.Unmarshal(resp.Data, &vs) + err = json.Unmarshal(resp.Data, &vs) if err != nil { v.client.logger.Error("error while parsing json", "request", "GetVehicleStatus", "error", err.Error()) } @@ -855,6 +784,81 @@ func (v *Vehicle) GetFeaturesList() { } } +// executeServiceRequest +func (v *Vehicle) executeServiceRequest(params map[string]string, reqUrl, pollingUrl string, ch chan string, attempt int) error { + var maxAttempts = 15 + + if attempt >= maxAttempts { + v.client.logger.Error("maximum attempts reached for service request", "request", reqUrl, "attempts", attempt) + ch <- "error" + return errors.New("maximum attempts reached for service request") + } + + if v.Vin != v.client.currentVin { + v.selectVehicle() + } + + var resp *Response + var err error + if attempt == 1 { + resp, err = v.client.execute(POST, reqUrl, params, true) + if err != nil { + v.client.logger.Error("error while executing service request", "request", reqUrl, "error", err.Error()) + ch <- "error" + return err + } + } else { + resp, err = v.client.execute(GET, pollingUrl, params, false) + if err != nil { + v.client.logger.Error("error while executing service request status polling", "request", reqUrl, "error", err.Error()) + ch <- "error" + return err + } + } + + // dataName field has the list of the states [ remoteServiceStatus | errorResponse ] + if resp.DataName == "remoteServiceStatus" { + if sr, ok := v.parseServiceRequest([]byte(resp.Data)); ok { + ch <- sr.RemoteServiceState + switch sr.RemoteServiceState { + + case "finished": + // Finished RemoteServiceState Service Request does not include Service Request ID + v.client.logger.Debug("Remote service request completed successfully") + + case "started": + time.Sleep(5 * time.Second) + v.client.logger.Debug("Subaru API reports remote service request (started) is in progress", "id", sr.ServiceRequestID) + v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1) + + case "stopping": + time.Sleep(5 * time.Second) + v.client.logger.Debug("Subaru API reports remote service request (stopping) is in progress", "id", sr.ServiceRequestID) + v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1) + + default: + v.client.logger.Debug("Subaru API reports remote service request (default)") + v.executeServiceRequest(map[string]string{"serviceRequestId": sr.ServiceRequestID}, reqUrl, pollingUrl, ch, attempt+1) + } + return nil + } + v.client.logger.Error("error while parsing service request json", "request", reqUrl, "response", resp.Data) + return errors.New("error while parsing service request json") + } + return errors.New("response is not a service request") +} + +// parseServiceRequest . +func (v *Vehicle) parseServiceRequest(b []byte) (ServiceRequest, bool) { + var sr ServiceRequest + err := json.Unmarshal(b, &sr) + if err != nil { + v.client.logger.Error("error while parsing service request json", "error", err.Error()) + return sr, false + } + return sr, true +} + // selectVehicle . func (v *Vehicle) selectVehicle() { if v.client.currentVin != v.Vin { diff --git a/vehicle_test.go b/vehicle_test.go new file mode 100644 index 0000000..4bcd5a7 --- /dev/null +++ b/vehicle_test.go @@ -0,0 +1,265 @@ +package mysubaru + +import ( + "fmt" + "net/http" + "testing" +) + +func TestGetClimatePresets_Success(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { + // Handle API_LOGIN endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_LOGIN"] && r.Method == http.MethodPost { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","enableXtime":true,"termsAndConditionsAccepted":true,"digitalGlobeConnectId":"0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeImageTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeTransparentTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","tomtomKey":"DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`) + } + // Handle SELECT_VEHICLE endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"vehicle","data":{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":"2023","modelCode":"PDL","engineSize":2.4,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"8KV8","licensePlateState":"NJ","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":["REMOTE","SAFETY","Retail3"],"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":["ABS_MIL","ACCS","AHBL_MIL","ATF_MIL","AWD_MIL","BSD","BSDRCT_MIL","CEL_MIL","CP1_5HHU","EBD_MIL","EOL_MIL","EPAS_MIL","EPB_MIL","ESS_MIL","EYESIGHT","ISS_MIL","MOONSTAT","OPL_MIL","PANPM-TUIRWAOC","PWAAADWWAP","RAB_MIL","RCC","REARBRK","RES","RESCC","RES_HVAC_HFS","RES_HVAC_VFS","RHSF","RPOI","RPOIA","RTGU","RVFS","SRH_MIL","SRS_MIL","SXM360L","T23DCM","TEL_MIL","TIF_35","TIR_33","TLD","TPMS_MIL","VALET","VDC_MIL","WASH_MIL","WDWSTAT","g3"],"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":"ACTIVE","authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":"Outback","subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":"Cosmic Blue Pearl","sunsetUpgraded":true,"intDescrip":"Black","transCode":"CVT","provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}}`) + } + // Handle API_G2_FETCH_RES_SUBARU_PRESETS endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_G2_FETCH_RES_SUBARU_PRESETS"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":["{\"name\": \"Auto\", \"runTimeMinutes\": \"10\", \"climateZoneFrontTemp\": \"74\", \"climateZoneFrontAirMode\": \"AUTO\", \"climateZoneFrontAirVolume\": \"AUTO\", \"outerAirCirculation\": \"auto\", \"heatedRearWindowActive\": \"false\", \"airConditionOn\": \"false\", \"heatedSeatFrontLeft\": \"off\", \"heatedSeatFrontRight\": \"off\", \"startConfiguration\": \"START_ENGINE_ALLOW_KEY_IN_IGNITION\", \"canEdit\": \"true\", \"disabled\": \"false\", \"vehicleType\": \"gas\", \"presetType\": \"subaruPreset\" }","{\"name\":\"Full Cool\",\"runTimeMinutes\":\"10\",\"climateZoneFrontTemp\":\"60\",\"climateZoneFrontAirMode\":\"feet_face_balanced\",\"climateZoneFrontAirVolume\":\"7\",\"airConditionOn\":\"true\",\"heatedSeatFrontLeft\":\"high_cool\",\"heatedSeatFrontRight\":\"high_cool\",\"heatedRearWindowActive\":\"false\",\"outerAirCirculation\":\"outsideAir\",\"startConfiguration\":\"START_ENGINE_ALLOW_KEY_IN_IGNITION\",\"canEdit\":\"true\",\"disabled\":\"true\",\"vehicleType\":\"gas\",\"presetType\":\"subaruPreset\"}","{\"name\": \"Full Heat\", \"runTimeMinutes\": \"10\", \"climateZoneFrontTemp\": \"85\", \"climateZoneFrontAirMode\": \"feet_window\", \"climateZoneFrontAirVolume\": \"7\", \"airConditionOn\": \"false\", \"heatedSeatFrontLeft\": \"high_heat\", \"heatedSeatFrontRight\": \"high_heat\", \"heatedRearWindowActive\": \"true\", \"outerAirCirculation\": \"outsideAir\", \"startConfiguration\": \"START_ENGINE_ALLOW_KEY_IN_IGNITION\", \"canEdit\": \"true\", \"disabled\": \"true\", \"vehicleType\": \"gas\", \"presetType\": \"subaruPreset\" }","{\"name\": \"Full Cool\", \"runTimeMinutes\": \"10\", \"climateZoneFrontTemp\": \"60\", \"climateZoneFrontAirMode\": \"feet_face_balanced\", \"climateZoneFrontAirVolume\": \"7\", \"airConditionOn\": \"true\", \"heatedSeatFrontLeft\": \"OFF\", \"heatedSeatFrontRight\": \"OFF\", \"heatedRearWindowActive\": \"false\", \"outerAirCirculation\": \"outsideAir\", \"startConfiguration\": \"START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION\", \"canEdit\": \"true\", \"disabled\": \"true\", \"vehicleType\": \"phev\", \"presetType\": \"subaruPreset\" }","{\"name\": \"Full Heat\", \"runTimeMinutes\": \"10\", \"climateZoneFrontTemp\": \"85\", \"climateZoneFrontAirMode\": \"feet_window\", \"climateZoneFrontAirVolume\": \"7\", \"airConditionOn\": \"false\", \"heatedSeatFrontLeft\": \"high_heat\", \"heatedSeatFrontRight\": \"high_heat\", \"heatedRearWindowActive\": \"true\", \"outerAirCirculation\": \"outsideAir\", \"startConfiguration\": \"START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION\", \"canEdit\": \"true\", \"disabled\": \"true\", \"vehicleType\": \"phev\", \"presetType\": \"subaruPreset\" }"]}`) + } + } + + ts := mockMySubaruApi(t, handler) + ts.Start() + defer ts.Close() + + cfg := makeConfig(t) + + msc, err := New(cfg) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if msc == nil { + t.Fatalf("expected MySubaru API client, got nil") + } + if !msc.isAuthenticated || !msc.isRegistered { + t.Errorf("expected authenticated and registered true, got %v %v", msc.isAuthenticated, msc.isRegistered) + } + if msc.currentVin != "1HGCM82633A004352" { + t.Errorf("expected currentVin 1HGCM82633A004352, got %v", msc.currentVin) + } +} + +func TestGetClimateQuickPresets_Success(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { + // Handle API_LOGIN endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_LOGIN"] && r.Method == http.MethodPost { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","enableXtime":true,"termsAndConditionsAccepted":true,"digitalGlobeConnectId":"0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeImageTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeTransparentTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","tomtomKey":"DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`) + } + // Handle SELECT_VEHICLE endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"vehicle","data":{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":"2023","modelCode":"PDL","engineSize":2.4,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"8KV8","licensePlateState":"NJ","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":["REMOTE","SAFETY","Retail3"],"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":["ABS_MIL","ACCS","AHBL_MIL","ATF_MIL","AWD_MIL","BSD","BSDRCT_MIL","CEL_MIL","CP1_5HHU","EBD_MIL","EOL_MIL","EPAS_MIL","EPB_MIL","ESS_MIL","EYESIGHT","ISS_MIL","MOONSTAT","OPL_MIL","PANPM-TUIRWAOC","PWAAADWWAP","RAB_MIL","RCC","REARBRK","RES","RESCC","RES_HVAC_HFS","RES_HVAC_VFS","RHSF","RPOI","RPOIA","RTGU","RVFS","SRH_MIL","SRS_MIL","SXM360L","T23DCM","TEL_MIL","TIF_35","TIR_33","TLD","TPMS_MIL","VALET","VDC_MIL","WASH_MIL","WDWSTAT","g3"],"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":"ACTIVE","authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":"Outback","subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":"Cosmic Blue Pearl","sunsetUpgraded":true,"intDescrip":"Black","transCode":"CVT","provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}}`) + } + // Handle API_G2_FETCH_RES_QUICK_START_SETTINGS endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_G2_FETCH_RES_QUICK_START_SETTINGS"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":"{\"name\":\"Cooling\",\"runTimeMinutes\":\"10\",\"climateZoneFrontTemp\":\"65\",\"climateZoneFrontAirMode\":\"FEET_FACE_BALANCED\",\"climateZoneFrontAirVolume\":\"7\",\"outerAirCirculation\":\"outsideAir\",\"heatedRearWindowActive\":\"false\",\"heatedSeatFrontLeft\":\"HIGH_COOL\",\"airConditionOn\":\"false\",\"canEdit\":\"true\",\"disabled\":\"false\",\"presetType\":\"userPreset\",\"startConfiguration\":\"START_ENGINE_ALLOW_KEY_IN_IGNITION\"}"}`) + } + } + + ts := mockMySubaruApi(t, handler) + ts.Start() + defer ts.Close() + + cfg := makeConfig(t) + + msc, err := New(cfg) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if msc == nil { + t.Fatalf("expected MySubaru API client, got nil") + } + if !msc.isAuthenticated || !msc.isRegistered { + t.Errorf("expected authenticated and registered true, got %v %v", msc.isAuthenticated, msc.isRegistered) + } + if msc.currentVin != "1HGCM82633A004352" { + t.Errorf("expected currentVin 1HGCM82633A004352, got %v", msc.currentVin) + } +} + +func TestGetClimateUserPresets_Success(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { + // Handle API_LOGIN endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_LOGIN"] && r.Method == http.MethodPost { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","enableXtime":true,"termsAndConditionsAccepted":true,"digitalGlobeConnectId":"0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeImageTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeTransparentTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","tomtomKey":"DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`) + } + // Handle SELECT_VEHICLE endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"vehicle","data":{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":"2023","modelCode":"PDL","engineSize":2.4,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"8KV8","licensePlateState":"NJ","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":["REMOTE","SAFETY","Retail3"],"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":["ABS_MIL","ACCS","AHBL_MIL","ATF_MIL","AWD_MIL","BSD","BSDRCT_MIL","CEL_MIL","CP1_5HHU","EBD_MIL","EOL_MIL","EPAS_MIL","EPB_MIL","ESS_MIL","EYESIGHT","ISS_MIL","MOONSTAT","OPL_MIL","PANPM-TUIRWAOC","PWAAADWWAP","RAB_MIL","RCC","REARBRK","RES","RESCC","RES_HVAC_HFS","RES_HVAC_VFS","RHSF","RPOI","RPOIA","RTGU","RVFS","SRH_MIL","SRS_MIL","SXM360L","T23DCM","TEL_MIL","TIF_35","TIR_33","TLD","TPMS_MIL","VALET","VDC_MIL","WASH_MIL","WDWSTAT","g3"],"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":"ACTIVE","authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":"Outback","subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":"Cosmic Blue Pearl","sunsetUpgraded":true,"intDescrip":"Black","transCode":"CVT","provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}}`) + } + // Handle API_G2_FETCH_RES_USER_PRESETS endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_G2_FETCH_RES_USER_PRESETS"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":"[{\"name\":\"Cooling\",\"runTimeMinutes\":\"10\",\"climateZoneFrontTemp\":\"65\",\"climateZoneFrontAirMode\":\"FEET_FACE_BALANCED\",\"climateZoneFrontAirVolume\":\"7\",\"outerAirCirculation\":\"outsideAir\",\"heatedRearWindowActive\":\"false\",\"heatedSeatFrontLeft\":\"HIGH_COOL\",\"heatedSeatFrontRight\":\"HIGH_COOL\",\"airConditionOn\":\"false\",\"canEdit\":\"true\",\"disabled\":\"false\",\"presetType\":\"userPreset\",\"startConfiguration\":\"START_ENGINE_ALLOW_KEY_IN_IGNITION\"}]"}`) + } + } + + ts := mockMySubaruApi(t, handler) + ts.Start() + defer ts.Close() + + cfg := makeConfig(t) + + msc, err := New(cfg) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if msc == nil { + t.Fatalf("expected MySubaru API client, got nil") + } + if !msc.isAuthenticated || !msc.isRegistered { + t.Errorf("expected authenticated and registered true, got %v %v", msc.isAuthenticated, msc.isRegistered) + } + if msc.currentVin != "1HGCM82633A004352" { + t.Errorf("expected currentVin 1HGCM82633A004352, got %v", msc.currentVin) + } +} + +func TestGetVehicleStatus_Success(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { + // Handle API_LOGIN endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_LOGIN"] && r.Method == http.MethodPost { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","enableXtime":true,"termsAndConditionsAccepted":true,"digitalGlobeConnectId":"0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeImageTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeTransparentTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","tomtomKey":"DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`) + } + // Handle SELECT_VEHICLE endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"vehicle","data":{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":"2023","modelCode":"PDL","engineSize":2.4,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"8KV8","licensePlateState":"NJ","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":["REMOTE","SAFETY","Retail3"],"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":["ABS_MIL","ACCS","AHBL_MIL","ATF_MIL","AWD_MIL","BSD","BSDRCT_MIL","CEL_MIL","CP1_5HHU","EBD_MIL","EOL_MIL","EPAS_MIL","EPB_MIL","ESS_MIL","EYESIGHT","ISS_MIL","MOONSTAT","OPL_MIL","PANPM-TUIRWAOC","PWAAADWWAP","RAB_MIL","RCC","REARBRK","RES","RESCC","RES_HVAC_HFS","RES_HVAC_VFS","RHSF","RPOI","RPOIA","RTGU","RVFS","SRH_MIL","SRS_MIL","SXM360L","T23DCM","TEL_MIL","TIF_35","TIR_33","TLD","TPMS_MIL","VALET","VDC_MIL","WASH_MIL","WDWSTAT","g3"],"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":"ACTIVE","authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":"Outback","subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":"Cosmic Blue Pearl","sunsetUpgraded":true,"intDescrip":"Black","transCode":"CVT","provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}}`) + } + // Handle API_VEHICLE_STATUS endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_VEHICLE_STATUS"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":{"vhsId":14662115789,"odometerValue":31694,"odometerValueKilometers":50996,"eventDate":1751742945000,"eventDateStr":"2025-07-05T19:15+0000","eventDateCarUser":1751742945000,"eventDateStrCarUser":"2025-07-05T19:15+0000","latitude":40.700153,"longitude":-74.401405,"positionHeadingDegree":"154","tirePressureFrontLeft":"2482","tirePressureFrontRight":"2482","tirePressureRearLeft":"2413","tirePressureRearRight":"2482","tirePressureFrontLeftPsi":"36","tirePressureFrontRightPsi":"36","tirePressureRearLeftPsi":"35","tirePressureRearRightPsi":"36","doorBootPosition":"CLOSED","doorEngineHoodPosition":"CLOSED","doorFrontLeftPosition":"CLOSED","doorFrontRightPosition":"CLOSED","doorRearLeftPosition":"CLOSED","doorRearRightPosition":"CLOSED","doorBootLockStatus":"LOCKED","doorFrontLeftLockStatus":"LOCKED","doorFrontRightLockStatus":"LOCKED","doorRearLeftLockStatus":"LOCKED","doorRearRightLockStatus":"LOCKED","distanceToEmptyFuelMiles":259.73,"distanceToEmptyFuelKilometers":418,"avgFuelConsumptionMpg":102.2,"avgFuelConsumptionLitersPer100Kilometers":2.3,"evStateOfChargePercent":null,"evDistanceToEmptyMiles":null,"evDistanceToEmptyKilometers":null,"evDistanceToEmptyByStateMiles":null,"evDistanceToEmptyByStateKilometers":null,"vehicleStateType":"IGNITION_OFF","windowFrontLeftStatus":"CLOSE","windowFrontRightStatus":"CLOSE","windowRearLeftStatus":"CLOSE","windowRearRightStatus":"CLOSE","windowSunroofStatus":"CLOSE","tyreStatusFrontLeft":"UNKNOWN","tyreStatusFrontRight":"UNKNOWN","tyreStatusRearLeft":"UNKNOWN","tyreStatusRearRight":"UNKNOWN","remainingFuelPercent":90,"distanceToEmptyFuelMiles10s":260,"distanceToEmptyFuelKilometers10s":420}}`) + } + } + + ts := mockMySubaruApi(t, handler) + ts.Start() + defer ts.Close() + + cfg := makeConfig(t) + + msc, err := New(cfg) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if msc == nil { + t.Fatalf("expected MySubaru API client, got nil") + } + if !msc.isAuthenticated || !msc.isRegistered { + t.Errorf("expected authenticated and registered true, got %v %v", msc.isAuthenticated, msc.isRegistered) + } + if msc.currentVin != "1HGCM82633A004352" { + t.Errorf("expected currentVin 1HGCM82633A004352, got %v", msc.currentVin) + } +} + +func TestGetVehicleCondition_Success(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { + // Handle API_LOGIN endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_LOGIN"] && r.Method == http.MethodPost { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","enableXtime":true,"termsAndConditionsAccepted":true,"digitalGlobeConnectId":"0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeImageTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeTransparentTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","tomtomKey":"DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`) + } + // Handle SELECT_VEHICLE endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"vehicle","data":{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":"2023","modelCode":"PDL","engineSize":2.4,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"8KV8","licensePlateState":"NJ","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":["REMOTE","SAFETY","Retail3"],"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":["ABS_MIL","ACCS","AHBL_MIL","ATF_MIL","AWD_MIL","BSD","BSDRCT_MIL","CEL_MIL","CP1_5HHU","EBD_MIL","EOL_MIL","EPAS_MIL","EPB_MIL","ESS_MIL","EYESIGHT","ISS_MIL","MOONSTAT","OPL_MIL","PANPM-TUIRWAOC","PWAAADWWAP","RAB_MIL","RCC","REARBRK","RES","RESCC","RES_HVAC_HFS","RES_HVAC_VFS","RHSF","RPOI","RPOIA","RTGU","RVFS","SRH_MIL","SRS_MIL","SXM360L","T23DCM","TEL_MIL","TIF_35","TIR_33","TLD","TPMS_MIL","VALET","VDC_MIL","WASH_MIL","WDWSTAT","g3"],"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":"ACTIVE","authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":"Outback","subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":"Cosmic Blue Pearl","sunsetUpgraded":true,"intDescrip":"Black","transCode":"CVT","provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}}`) + } + // Handle API_CONDITION endpoint + if (r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_CONDITION"], "g1") || r.URL.Path == MOBILE_API_VERSION+urlToGen(apiURLs["API_CONDITION"], "g2")) && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"remoteServiceStatus","data":{"serviceRequestId":null,"success":true,"cancelled":false,"remoteServiceType":"condition","remoteServiceState":"finished","subState":null,"errorCode":null,"result":{"avgFuelConsumption":null,"avgFuelConsumptionUnit":"MPG","distanceToEmptyFuel":null,"distanceToEmptyFuelUnit":"MILES","odometer":31692,"odometerUnit":"MILES","tirePressureFrontLeft":null,"tirePressureFrontLeftUnit":"PSI","tirePressureFrontRight":null,"tirePressureFrontRightUnit":"PSI","tirePressureRearLeft":null,"tirePressureRearLeftUnit":"PSI","tirePressureRearRight":null,"tirePressureRearRightUnit":"PSI","lastUpdatedTime":"2025-07-05T19:15:45.000+0000","windowFrontLeftStatus":"CLOSE","windowFrontRightStatus":"CLOSE","windowRearLeftStatus":"CLOSE","windowRearRightStatus":"CLOSE","windowSunroofStatus":"CLOSE","remainingFuelPercent":"90","evDistanceToEmpty":null,"evDistanceToEmptyUnit":null,"evChargerStateType":null,"evIsPluggedIn":null,"evStateOfChargeMode":null,"evTimeToFullyCharged":null,"evStateOfChargePercent":null,"vehicleStateType":"IGNITION_OFF","doorBootLockStatus":"LOCKED","doorBootPosition":"CLOSED","doorEngineHoodPosition":"CLOSED","doorFrontLeftLockStatus":"LOCKED","doorFrontLeftPosition":"CLOSED","doorFrontRightLockStatus":"LOCKED","doorFrontRightPosition":"CLOSED","doorRearLeftLockStatus":"LOCKED","doorRearLeftPosition":"CLOSED","doorRearRightLockStatus":"LOCKED","doorRearRightPosition":"CLOSED"},"updateTime":null,"vin":"1HGCM82633A004352","errorDescription":null}}`) + } + } + + ts := mockMySubaruApi(t, handler) + ts.Start() + defer ts.Close() + + cfg := makeConfig(t) + + msc, err := New(cfg) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if msc == nil { + t.Fatalf("expected MySubaru API client, got nil") + } + if !msc.isAuthenticated || !msc.isRegistered { + t.Errorf("expected authenticated and registered true, got %v %v", msc.isAuthenticated, msc.isRegistered) + } + if msc.currentVin != "1HGCM82633A004352" { + t.Errorf("expected currentVin 1HGCM82633A004352, got %v", msc.currentVin) + } +} + +func TestGetVehicleHealth_Success(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { + // Handle API_LOGIN endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_LOGIN"] && r.Method == http.MethodPost { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":"BIOMETRICS_DISABLED","dataName":"sessionData","data":{"sessionChanged":false,"vehicleInactivated":false,"account":{"marketId":1,"createdDate":1476984644000,"firstName":"Tatiana","lastName":"Savin","zipCode":"07974","accountKey":765268,"lastLoginDate":1751738613000,"zipCode5":"07974"},"resetPassword":false,"deviceId":"JddMBQXvAkgutSmEP6uFsThbq4QgEBBQ","sessionId":"9D7FCDF274794346689D3FA0D693CBBF","deviceRegistered":true,"passwordToken":null,"vehicles":[{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":null,"modelCode":null,"engineSize":null,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"","licensePlateState":"","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":null,"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":null,"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":null,"authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":null,"subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":null,"sunsetUpgraded":true,"intDescrip":null,"transCode":null,"provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}],"rightToRepairEnabled":true,"rightToRepairStartYear":2022,"rightToRepairStates":"MA","enableXtime":true,"termsAndConditionsAccepted":true,"digitalGlobeConnectId":"0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeImageTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe:ImageryTileService@EPSG:3857@png/{z}/{x}/{y}.png?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","digitalGlobeTransparentTileService":"https://earthwatch.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/Digitalglobe:OSMTransparentTMSTileService@EPSG:3857@png/{z}/{x}/{-y}.png/?connectId=0572e32b-2fcf-4bc8-abe0-1e3da8767132","tomtomKey":"DHH9SwEQ4MW55Hj2TfqMeldbsDjTdgAs","currentVehicleIndex":0,"handoffToken":"$2a$08$rOb/uqhm8I3QtSel2phOCOxNM51w43eqXDDksMkJ.1a5KsaQuLvEu$1751745334477","satelliteViewEnabled":true,"registeredDevicePermanent":true}}`) + } + // Handle SELECT_VEHICLE endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_SELECT_VEHICLE"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":"vehicle","data":{"customer":{"sessionCustomer":null,"email":null,"firstName":null,"lastName":null,"zip":null,"oemCustId":null,"phone":null},"vehicleName":"Subaru Outback TXT","stolenVehicle":false,"vin":"1HGCM82633A004352","modelYear":"2023","modelCode":"PDL","engineSize":2.4,"nickname":"Subaru Outback TXT","vehicleKey":8211380,"active":true,"licensePlate":"8KV8","licensePlateState":"NJ","email":null,"firstName":null,"lastName":null,"subscriptionFeatures":["REMOTE","SAFETY","Retail3"],"accessLevel":-1,"zip":null,"oemCustId":"CRM-41PLM-5TYE","vehicleMileage":null,"phone":null,"timeZone":"America/New_York","features":["ABS_MIL","ACCS","AHBL_MIL","ATF_MIL","AWD_MIL","BSD","BSDRCT_MIL","CEL_MIL","CP1_5HHU","EBD_MIL","EOL_MIL","EPAS_MIL","EPB_MIL","ESS_MIL","EYESIGHT","ISS_MIL","MOONSTAT","OPL_MIL","PANPM-TUIRWAOC","PWAAADWWAP","RAB_MIL","RCC","REARBRK","RES","RESCC","RES_HVAC_HFS","RES_HVAC_VFS","RHSF","RPOI","RPOIA","RTGU","RVFS","SRH_MIL","SRS_MIL","SXM360L","T23DCM","TEL_MIL","TIF_35","TIR_33","TLD","TPMS_MIL","VALET","VDC_MIL","WASH_MIL","WDWSTAT","g3"],"userOemCustId":"CRM-41PLM-5TYE","subscriptionStatus":"ACTIVE","authorizedVehicle":false,"preferredDealer":null,"cachedStateCode":"NJ","modelName":"Outback","subscriptionPlans":[],"crmRightToRepair":false,"needMileagePrompt":false,"phev":null,"extDescrip":"Cosmic Blue Pearl","sunsetUpgraded":true,"intDescrip":"Black","transCode":"CVT","provisioned":true,"remoteServicePinExist":true,"needEmergencyContactPrompt":false,"vehicleGeoPosition":null,"show3gSunsetBanner":false}}`) + } + // Handle API_VEHICLE_HEALTH endpoint + if r.URL.Path == MOBILE_API_VERSION+apiURLs["API_VEHICLE_HEALTH"] && r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"success":true,"errorCode":null,"dataName":null,"data":{"lastUpdatedDate":1751742945000,"vehicleHealthItems":[{"warningCode":10,"b2cCode":"airbag","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"SRS_MIL"},{"warningCode":4,"b2cCode":"oilTemp","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"ATF_MIL"},{"warningCode":39,"b2cCode":"blindspot","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"BSDRCT_MIL"},{"warningCode":2,"b2cCode":"engineFail","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"CEL_MIL"},{"warningCode":44,"b2cCode":"pkgBrake","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"EPB_MIL"},{"warningCode":8,"b2cCode":"ebd","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"EBD_MIL"},{"warningCode":3,"b2cCode":"oilWarning","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"EOL_MIL"},{"warningCode":1,"b2cCode":"washer","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"WASH_MIL"},{"warningCode":50,"b2cCode":"iss","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"ISS_MIL"},{"warningCode":53,"b2cCode":"oilPres","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"OPL_MIL"},{"warningCode":11,"b2cCode":"epas","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"EPAS_MIL"},{"warningCode":69,"b2cCode":"revBrake","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"RAB_MIL"},{"warningCode":14,"b2cCode":"telematics","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"TEL_MIL"},{"warningCode":9,"b2cCode":"tpms","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"TPMS_MIL"},{"warningCode":7,"b2cCode":"vdc","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"VDC_MIL"},{"warningCode":6,"b2cCode":"abs","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"ABS_MIL"},{"warningCode":5,"b2cCode":"awd","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"AWD_MIL"},{"warningCode":12,"b2cCode":"eyesight","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"ESS_MIL"},{"warningCode":30,"b2cCode":"ahbl","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"AHBL_MIL"},{"warningCode":31,"b2cCode":"srh","isTrouble":false,"onDates":[],"onDaiId":0,"featureCode":"SRH_MIL"}]}}`) + } + } + + ts := mockMySubaruApi(t, handler) + ts.Start() + defer ts.Close() + + cfg := makeConfig(t) + + msc, err := New(cfg) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if msc == nil { + t.Fatalf("expected MySubaru API client, got nil") + } + if !msc.isAuthenticated || !msc.isRegistered { + t.Errorf("expected authenticated and registered true, got %v %v", msc.isAuthenticated, msc.isRegistered) + } + if msc.currentVin != "1HGCM82633A004352" { + t.Errorf("expected currentVin 1HGCM82633A004352, got %v", msc.currentVin) + } +}