Refactor code structure and remove redundant sections for improved readability and maintainability
All checks were successful
Golan Testing / testing (1.24.x, ubuntu-latest) (push) Successful in 26s

This commit is contained in:
2025-07-06 14:23:35 -04:00
parent b61b5664b7
commit 152eb2c7b7
9 changed files with 813 additions and 156 deletions

View File

@ -20,16 +20,47 @@ The following samples will assist you to become as comfortable as possible with
import "git.savin.nyc/alex/mysubaru" import "git.savin.nyc/alex/mysubaru"
``` ```
#### Create a new MySubaru connection and get a car by VIN #### Create a new MySubaru API Client
```go ```go
// Create a MySubaru Client // Create a MySubaru Client
mysubaru, _ := New() msc, err := mysubaru.New(cfg)
outback := mysubaru.GetVehicleByVIN("VIN-CODE-HERE") 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 #### Start/Stop Lights request
```go ```go
outback.LightsStart() // Execute a LightsStart command
time.Sleep(30 * time.Second) events, err := outback.LightsStart()
outback.LightsStop() 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)
}
``` ```

View File

@ -83,7 +83,7 @@ func New(config *config.Config) (*Client, error) {
} }
client.currentVin = client.listOfVins[0] client.currentVin = client.listOfVins[0]
} else { } 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 nil, err
} }
return client, nil return client, nil
@ -94,19 +94,21 @@ func (c *Client) SelectVehicle(vin string) (*VehicleData, error) {
if vin == "" { if vin == "" {
vin = c.currentVin vin = c.currentVin
} }
vinCheck(vin) vinCheck(vin)
params := map[string]string{ params := map[string]string{
"vin": vin, "vin": vin,
"_": timestamp()} "_": timestamp()}
reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"] reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"]
// TODO: Add error handling resp, err := c.execute(GET, reqURL, params, false)
resp, _ := 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) // c.logger.Debug("http request output", "request", "SelectVehicle", "body", resp)
var vd VehicleData var vd VehicleData
err := json.Unmarshal(resp.Data, &vd) err = json.Unmarshal(resp.Data, &vd)
if err != nil { if err != nil {
c.logger.Error("error while parsing json", "request", "SelectVehicle", "error", err.Error()) c.logger.Error("error while parsing json", "request", "SelectVehicle", "error", err.Error())
return nil, errors.New("error while parsing json while vehicle selection") return nil, errors.New("error while parsing json while vehicle selection")
@ -116,32 +118,36 @@ func (c *Client) SelectVehicle(vin string) (*VehicleData, error) {
} }
// GetVehicles . // GetVehicles .
func (c *Client) GetVehicles() []*Vehicle { func (c *Client) GetVehicles() ([]*Vehicle, error) {
var vehicles []*Vehicle var vehicles []*Vehicle
for _, vin := range c.listOfVins { for _, vin := range c.listOfVins {
vehicle, err := c.GetVehicleByVIN(vin) vehicle, err := c.GetVehicleByVin(vin)
if err != nil { 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) vehicles = append(vehicles, vehicle)
} }
return vehicles return vehicles, nil
} }
// GetVehicleByVIN . // GetVehicleByVIN .
func (c *Client) GetVehicleByVIN(vin string) (*Vehicle, error) { func (c *Client) GetVehicleByVin(vin string) (*Vehicle, error) {
var vehicle *Vehicle var vehicle *Vehicle
if slices.Contains(c.listOfVins, vin) { if slices.Contains(c.listOfVins, vin) {
params := map[string]string{ params := map[string]string{
"vin": vin, "vin": vin,
"_": timestamp()} "_": timestamp()}
reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"] reqURL := MOBILE_API_VERSION + apiURLs["API_SELECT_VEHICLE"]
// TODO: Add error handling resp, err := c.execute(GET, reqURL, params, false)
resp, _ := 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) // c.logger.Debug("http request output", "request", "GetVehicleByVIN", "body", resp)
var vd VehicleData var vd VehicleData
err := json.Unmarshal(resp.Data, &vd) err = json.Unmarshal(resp.Data, &vd)
if err != nil { if err != nil {
c.logger.Error("error while parsing json", "request", "GetVehicleByVIN", "error", err.Error()) c.logger.Error("error while parsing json", "request", "GetVehicleByVIN", "error", err.Error())
} }

328
client_test.go Normal file
View File

@ -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)
}
}

View File

@ -30,12 +30,12 @@ var apiURLs = map[string]string{
"API_2FA_AUTH_VERIFY": "/twoStepAuthVerify.json", "API_2FA_AUTH_VERIFY": "/twoStepAuthVerify.json",
"API_LOGIN": "/login.json", // Same API for g1 and g2 "API_LOGIN": "/login.json", // Same API for g1 and g2
"API_REFRESH_VEHICLES": "/refreshVehicles.json", "API_REFRESH_VEHICLES": "/refreshVehicles.json",
"API_SELECT_VEHICLE": "/selectVehicle.json", "API_SELECT_VEHICLE": "/selectVehicle.json", // Covered by test
"API_VALIDATE_SESSION": "/validateSession.json", "API_VALIDATE_SESSION": "/validateSession.json", // Covered by test
"API_VEHICLE_STATUS": "/vehicleStatus.json", "API_VEHICLE_STATUS": "/vehicleStatus.json", // Covered by test
"API_AUTHORIZE_DEVICE": "/authenticateDevice.json", "API_AUTHORIZE_DEVICE": "/authenticateDevice.json",
"API_NAME_DEVICE": "/nameThisDevice.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_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_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", "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": "/service/api_gen/lightsOnly/execute.json",
"API_LIGHTS_CANCEL": "/service/api_gen/lightsOnly/cancel.json", "API_LIGHTS_CANCEL": "/service/api_gen/lightsOnly/cancel.json",
"API_LIGHTS_STOP": "/service/api_gen/lightsOnly/stop.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_UPDATE": "/service/g1/vehicleLocate/execute.json", // Different API for g1 and g2
"API_G1_LOCATE_STATUS": "/service/g1/vehicleLocate/status.json", "API_G1_LOCATE_STATUS": "/service/g1/vehicleLocate/status.json",
"API_G1_HORN_LIGHTS_STATUS": "/service/g1/hornLights/status.json", // g1-Only API "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_UPDATE": "/service/g2/vehicleStatus/execute.json",
"API_G2_LOCATE_STATUS": "/service/g2/vehicleStatus/locationStatus.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_SEND_POI": "/service/g2/sendPoi/execute.json", // g2-Only API
"API_G2_SPEEDFENCE": "/service/g2/speedFence/execute.json", "API_G2_SPEEDFENCE": "/service/g2/speedFence/execute.json",
"API_G2_GEOFENCE": "/service/g2/geoFence/execute.json", "API_G2_GEOFENCE": "/service/g2/geoFence/execute.json",

View File

@ -10,5 +10,5 @@ mysubaru:
timezone: "America/New_York" timezone: "America/New_York"
logging: logging:
level: INFO level: INFO
output: json output: JSON
source: false source: false

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"time"
"git.savin.nyc/alex/mysubaru" "git.savin.nyc/alex/mysubaru"
"git.savin.nyc/alex/mysubaru/config" "git.savin.nyc/alex/mysubaru/config"
@ -21,9 +22,13 @@ func main() {
if err != nil { if err != nil {
log.Fatal(err) 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") // subaru := ms.SelectVehicle("4S4BTGPD0P3199198")
@ -37,48 +42,66 @@ func main() {
// fmt.Printf("GEN #1: %+v\n\n", subaru1.getAPIGen()) // 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 // 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") outback, err := msc.GetVehicleByVIN("4S4BTGPD0P3199198")
// subaru.GetLocation(true) 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() // // Execute a LightsStart command
fmt.Printf("SUBARU #1 (Vehicle Status):\n") // events, err := outback.LightsStart()
// subaru.GetVehicleStatus() // if err != nil {
// fmt.Printf("SUBARU #1 (Vehicle Condition):\n") // cfg.Logger.Error("cannot execute LightsStart command", "error", err)
// subaru.GetVehicleCondition() // os.Exit(1)
// fmt.Printf("SUBARU #1: %+v\n", subaru) // }
// subaru.GetClimatePresets() // for event := range events {
// subaru.GetClimateUserPresets() // fmt.Printf("Lights Start Event: %+v\n", event)
// fmt.Printf("SUBARU #2: %+v\n", subaru) // }
// subaru.GetVehicleHealth()
// subaru.GetFeaturesList()
// subaru := mysubaru.GetVehicles()[0]
// fmt.Printf("SUBARU: %+v\n", subaru) // // Wait for a while to see the lights on
// 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()
// time.Sleep(20 * time.Second) // time.Sleep(20 * time.Second)
// subaru.Lock()
// subaru.GetLocation() // // Execute a LightsStop command
fmt.Printf("SUBARU: %+v\n", outback) // 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)
// }
} }

View File

@ -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) { func TestUrlToGen(t *testing.T) {
tests := []struct { tests := []struct {
url, gen, want string 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) 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)
}
}

View File

@ -481,81 +481,6 @@ func (v *Vehicle) GetLocation(force bool) (chan string, error) {
return ch, nil 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. // GetClimatePresets connects to the MySubaru API to download available climate presets.
// It first attempts to establish a connection with the MySubaru API. // It first attempts to establish a connection with the MySubaru API.
// If successful and climate presets are found for the user's vehicle, // If successful and climate presets are found for the user's vehicle,
@ -710,11 +635,15 @@ func (v *Vehicle) GetVehicleStatus() error {
v.selectVehicle() v.selectVehicle()
} }
reqUrl := MOBILE_API_VERSION + urlToGen(apiURLs["API_VEHICLE_STATUS"], v.getAPIGen()) 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) // v.client.logger.Info("http request output", "request", "GetVehicleStatus", "body", resp)
var vs VehicleStatus var vs VehicleStatus
err := json.Unmarshal(resp.Data, &vs) err = json.Unmarshal(resp.Data, &vs)
if err != nil { if err != nil {
v.client.logger.Error("error while parsing json", "request", "GetVehicleStatus", "error", err.Error()) 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 . // selectVehicle .
func (v *Vehicle) selectVehicle() { func (v *Vehicle) selectVehicle() {
if v.client.currentVin != v.Vin { if v.client.currentVin != v.Vin {

265
vehicle_test.go Normal file
View File

@ -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)
}
}