Enhance documentation for API client and vehicle structures; improve test function naming for clarity
All checks were successful
Golan Testing / testing (1.24.x, ubuntu-latest) (push) Successful in 24s

This commit is contained in:
2025-07-06 15:15:12 -04:00
parent d8cf2c3fd7
commit 3c927fd83b
7 changed files with 61 additions and 64 deletions

View File

@ -12,7 +12,7 @@ import (
"resty.dev/v3"
)
// Client .
// Client represents a MySubaru API client that interacts with the MySubaru API.
type Client struct {
credentials config.Credentials
httpClient *resty.Client
@ -28,7 +28,7 @@ type Client struct {
sync.RWMutex
}
// New function creates a New MySubaru client
// New function creates a New MySubaru API client
func New(config *config.Config) (*Client, error) {
client := &Client{
@ -89,7 +89,7 @@ func New(config *config.Config) (*Client, error) {
return client, nil
}
// SelectVehicle .
// SelectVehicle selects a vehicle by its VIN. If no VIN is provided, it uses the current VIN.
func (c *Client) SelectVehicle(vin string) (*VehicleData, error) {
if vin == "" {
vin = c.currentVin
@ -117,7 +117,7 @@ func (c *Client) SelectVehicle(vin string) (*VehicleData, error) {
return &vd, nil
}
// GetVehicles .
// GetVehicles retrieves a list of vehicles associated with the client's account.
func (c *Client) GetVehicles() ([]*Vehicle, error) {
var vehicles []*Vehicle
for _, vin := range c.listOfVins {
@ -131,7 +131,7 @@ func (c *Client) GetVehicles() ([]*Vehicle, error) {
return vehicles, nil
}
// GetVehicleByVIN .
// GetVehicleByVin retrieves a vehicle by its VIN from the client's list of vehicles.
func (c *Client) GetVehicleByVin(vin string) (*Vehicle, error) {
var vehicle *Vehicle
if slices.Contains(c.listOfVins, vin) {
@ -203,15 +203,15 @@ func (c *Client) GetVehicleByVin(vin string) (*Vehicle, error) {
// func getRemoteStartStatus() {}
// func getSafetyStatus() {}
// func getSubscriptionStatus() {}
// func getClimateData() {}
// func saveClimateSettings() {}
// IsAlive checks if the Client instance is alive
func (c *Client) IsAlive() bool {
return c.isAlive
}
// Exec method executes a Client instance with the API URL
// execute executes an HTTP request based on the method, URL, and parameters provided.
func (c *Client) execute(method string, url string, params map[string]string, j bool) (*Response, error) {
c.Lock()
// defer timeTrack("[TIMETRK] Executing HTTP Request")
var resp *resty.Response
var err error
@ -263,6 +263,7 @@ func (c *Client) execute(method string, url string, params map[string]string, j
if r, ok := c.parseResponse(resBytes); ok {
c.isAlive = true
c.Unlock()
return &r, nil
} else {
if r.DataName == "errorResponse" {
@ -275,16 +276,18 @@ func (c *Client) execute(method string, url string, params map[string]string, j
c.logger.Error("request got an error", "request", "execute", "method", method, "url", url, "label", er.ErrorLabel, "descrip[tion", er.ErrorDescription)
}
c.logger.Error("request got an unknown error", "request", "execute", "method", method, "url", url, "label", er.ErrorLabel, "descrip[tion", er.ErrorDescription)
c.Unlock()
return nil, errors.New("request is not successfull, HTTP code: " + resp.Status())
}
c.logger.Error("request is not successfull", "request", "execute", "method", method, "url", url, "error", err.Error())
}
}
c.isAlive = false
c.Unlock()
return nil, errors.New("request is not successfull, HTTP code: " + resp.Status())
}
// auth .
// auth authenticates the client with the MySubaru API using the provided credentials.
func (c *Client) auth() (*Response, error) {
params := map[string]string{
"env": "cloudprod",
@ -306,7 +309,7 @@ func (c *Client) auth() (*Response, error) {
return resp, nil
}
// parseResponse .
// parseResponse parses the JSON response from the MySubaru API into a Response struct.
func (c *Client) parseResponse(b []byte) (Response, bool) {
var r Response
err := json.Unmarshal(b, &r)
@ -317,7 +320,7 @@ func (c *Client) parseResponse(b []byte) (Response, bool) {
return r, true
}
// ValidateSession .
// ValidateSession checks if the current session is valid by making a request to the vehicle status API.
func (c *Client) ValidateSession() bool {
reqURL := MOBILE_API_VERSION + apiURLs["API_VEHICLE_STATUS"]
resp, err := c.execute(GET, reqURL, map[string]string{}, false)

View File

@ -11,7 +11,7 @@ import (
"git.savin.nyc/alex/mysubaru/config"
)
func makeConfig(t *testing.T) *config.Config {
func mockConfig(t *testing.T) *config.Config {
return &config.Config{
MySubaru: config.MySubaru{
Credentials: config.Credentials{
@ -29,6 +29,7 @@ func makeConfig(t *testing.T) *config.Config {
}
}
// mockMySubaruApi creates a mock MySubaru API server for testing.
func mockMySubaruApi(t *testing.T, handler http.HandlerFunc) *httptest.Server {
// Create a listener with the desired port
@ -176,7 +177,7 @@ func TestNew_Success(t *testing.T) {
ts.Start()
defer ts.Close()
cfg := makeConfig(t)
cfg := mockConfig(t)
msc, err := New(cfg)
if err != nil {
@ -235,7 +236,7 @@ func TestSelectVehicle_Success(t *testing.T) {
ts.Start()
defer ts.Close()
cfg := makeConfig(t)
cfg := mockConfig(t)
msc, err := New(cfg)
if err != nil {
@ -309,7 +310,7 @@ func TestGetVehicleByVin_Success(t *testing.T) {
ts.Start()
defer ts.Close()
cfg := makeConfig(t)
cfg := mockConfig(t)
msc, err := New(cfg)
if err != nil {

View File

@ -4,7 +4,7 @@ import (
"encoding/json"
)
// Response .
// Response represents the structure of a response from the MySubaru API.
type Response struct {
Success bool `json:"success"` // true | false
ErrorCode string `json:"errorCode,omitempty"` // string | Error message if Success is false
@ -25,7 +25,7 @@ type Response struct {
// Unmarshal .
// func (r *Response) Unmarshal(b []byte) {}
// Request .
// Request represents the structure of a request to the MySubaru API.
type Request struct {
Vin string `json:"vin"` //
Pin string `json:"pin"` //

View File

@ -58,7 +58,7 @@ func vinCheck(vin string) (bool, string) {
return valid, retVin
}
// transcodeDigits .
// transcodeDigits transcodes VIN digits to a numeric value
func transcodeDigits(vin string) int {
var digitSum = 0
var code int
@ -114,30 +114,8 @@ func transcodeDigits(vin string) int {
return digitSum
}
// isNilFixed .
// func isNil(i interface{}) bool {
// if i == nil {
// return true
// }
// switch reflect.TypeOf(i).Kind() {
// case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice:
// return reflect.ValueOf(i).IsNil()
// }
// return false
// }
// timeTrack .
// func timeTrack(name string) {
// start := time.Now()
// fmt.Printf("%s took %v\n", name, time.Since(start))
// }
// contains .
// func contains(s []string, str string) bool {
// for _, v := range s {
// if v == str {
// return true
// }
// }
// return false
// }

View File

@ -7,6 +7,7 @@ import (
"time"
)
// timestamp returns the current time in milliseconds since epoch as a string.
func TestTimestamp(t *testing.T) {
ts1 := timestamp()
time.Sleep(1 * time.Millisecond)
@ -22,6 +23,7 @@ func TestTimestamp(t *testing.T) {
}
}
// timestamp returns the current time in milliseconds since epoch as a string.
func TestTimestamp_Format(t *testing.T) {
ts := timestamp()
matched, err := regexp.MatchString(`^\d+$`, ts)
@ -33,6 +35,7 @@ func TestTimestamp_Format(t *testing.T) {
}
}
// urlToGen replaces "api_gen" in the URL with the specified generation.
func TestUrlToGen(t *testing.T) {
tests := []struct {
url, gen, want string
@ -51,6 +54,7 @@ func TestUrlToGen(t *testing.T) {
}
}
// vinCheck validates the VIN check digit and returns the corrected VIN.
func TestVinCheck_Valid(t *testing.T) {
// Example valid VIN: 1HGCM82633A004352 (check digit is '3')
vin := "1HGCM82633A004352"
@ -63,6 +67,7 @@ func TestVinCheck_Valid(t *testing.T) {
}
}
// TestVinCheck_InvalidCheckDigit tests a VIN with an incorrect check digit.
func TestVinCheck_InvalidCheckDigit(t *testing.T) {
vin := "1HGCM82633A004352"
// Change check digit (9th char) to '9'
@ -77,6 +82,7 @@ func TestVinCheck_InvalidCheckDigit(t *testing.T) {
}
}
// TestVinCheck_WrongLength tests a VIN that is not 17 characters long.
func TestVinCheck_WrongLength(t *testing.T) {
vin := "1234567890123456" // 16 chars
valid, corrected := vinCheck(vin)
@ -88,6 +94,7 @@ func TestVinCheck_WrongLength(t *testing.T) {
}
}
// transcodeDigits computes the sum of the VIN digits according to the VIN rules.
func TestTranscodeDigits(t *testing.T) {
// Use a known VIN and manually compute the sum
vin := "1HGCM82633A004352"
@ -99,6 +106,7 @@ func TestTranscodeDigits(t *testing.T) {
}
}
// TestVinCheck_XCheckDigit tests a VIN with 'X' as the check digit.
func TestVinCheck_XCheckDigit(t *testing.T) {
// VIN with check digit 'X'
vin := "1M8GDM9AXKP042788"
@ -111,6 +119,7 @@ func TestVinCheck_XCheckDigit(t *testing.T) {
}
}
// TestUrlToGen_NoApiGen tests the case where the URL does not contain "api_gen".
func TestUrlToGen_NoApiGen(t *testing.T) {
url := "https://host/endpoint"
gen := "g1"

View File

@ -36,7 +36,7 @@ import (
// },
// }
// Vehicle .
// Vehicle represents a Subaru vehicle with various attributes and methods to interact with it.
type Vehicle struct {
CarId int64
Vin string // SELECT CAR REQUEST > "vin": "4S4BTGND8L3137058"
@ -88,7 +88,7 @@ type Vehicle struct {
}
// ClimateProfile .
// ClimateProfile represents a climate control profile for a Subaru vehicle.
type ClimateProfile struct {
Name string `json:"name,omitempty"`
VehicleType string `json:"vehicleType,omitempty"` // vehicleType [ gas | phev ]
@ -107,7 +107,7 @@ type ClimateProfile struct {
StartConfiguration string `json:"startConfiguration"` // startConfiguration [ START_ENGINE_ALLOW_KEY_IN_IGNITION (gas) | START_CLIMATE_CONTROL_ONLY_ALLOW_KEY_IN_IGNITION (phev) ]
}
// GeoLocation .
// GeoLocation represents the geographical location of a Subaru vehicle.
type GeoLocation struct {
Latitude float64 // 40.700184
Longitude float64 // -74.401375
@ -116,7 +116,7 @@ type GeoLocation struct {
Updated time.Time
}
// Door .
// Door represents a door of a Subaru vehicle with its position, sub-position, status, and lock state.
type Door struct {
Position string // front | rear | boot | enginehood
SubPosition string // right | left
@ -125,7 +125,7 @@ type Door struct {
Updated time.Time
}
// Window .
// Window represents a window of a Subaru vehicle with its position, sub-position, status, and last updated time.
type Window struct {
Position string
SubPosition string
@ -133,7 +133,7 @@ type Window struct {
Updated time.Time
}
// Tire .
// Tire represents a tire of a Subaru vehicle with its position, sub-position, pressure, pressure in PSI, and last updated time.
type Tire struct {
Position string
SubPosition string
@ -143,7 +143,7 @@ type Tire struct {
// Status string
}
// Trouble .
// Trouble represents a trouble or issue with a Subaru vehicle, containing a description of the trouble.
type Trouble struct {
Description string
}
@ -211,7 +211,7 @@ func (v *Vehicle) String() string {
return vString
}
// Lock .
// Lock
// Sends a command to lock doors.
func (v *Vehicle) Lock() (chan string, error) {
if !v.getRemoteOptionsStatus() {
@ -235,7 +235,7 @@ func (v *Vehicle) Lock() (chan string, error) {
return ch, nil
}
// Unlock .
// Unlock
// Send command to unlock doors.
func (v *Vehicle) Unlock() (chan string, error) {
if !v.getRemoteOptionsStatus() {
@ -259,7 +259,7 @@ func (v *Vehicle) Unlock() (chan string, error) {
return ch, nil
}
// EngineStart .
// EngineStart
// Sends a command to start engine and set climate control.
func (v *Vehicle) EngineStart() (chan string, error) {
if !v.getRemoteOptionsStatus() {
@ -296,7 +296,7 @@ func (v *Vehicle) EngineStart() (chan string, error) {
return ch, nil
}
// EngineStop .
// EngineStop
// Sends a command to stop engine.
func (v *Vehicle) EngineStop() (chan string, error) {
if !v.getRemoteOptionsStatus() {
@ -320,7 +320,7 @@ func (v *Vehicle) EngineStop() (chan string, error) {
return ch, nil
}
// LightsStart .
// LightsStart
// Sends a command to flash lights.
func (v *Vehicle) LightsStart() (chan string, error) {
if !v.getRemoteOptionsStatus() {
@ -345,7 +345,7 @@ func (v *Vehicle) LightsStart() (chan string, error) {
return ch, nil
}
// LightsStop .
// LightsStop
// Sends a command to stop flash lights.
func (v *Vehicle) LightsStop() (chan string, error) {
if !v.getRemoteOptionsStatus() {
@ -371,7 +371,7 @@ func (v *Vehicle) LightsStop() (chan string, error) {
return ch, nil
}
// HornStart .
// HornStart
// Send command to sound horn.
func (v *Vehicle) HornStart() (chan string, error) {
if !v.getRemoteOptionsStatus() {
@ -397,7 +397,7 @@ func (v *Vehicle) HornStart() (chan string, error) {
return ch, nil
}
// HornStop .
// HornStop
// Send command to sound horn.
func (v *Vehicle) HornStop() (chan string, error) {
if !v.getRemoteOptionsStatus() {
@ -423,7 +423,7 @@ func (v *Vehicle) HornStop() (chan string, error) {
return ch, nil
}
// ChargeStart .
// ChargeStart
func (v *Vehicle) ChargeOn() (chan string, error) {
if !v.getRemoteOptionsStatus() {
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
@ -447,7 +447,7 @@ func (v *Vehicle) ChargeOn() (chan string, error) {
}
}
// GetLocation .
// GetLocation
func (v *Vehicle) GetLocation(force bool) (chan string, error) {
if !v.getRemoteOptionsStatus() {
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])
@ -580,7 +580,7 @@ func (v *Vehicle) GetClimateQuickPresets() error {
return nil
}
// GetClimateUserPresets .
// GetClimateUserPresets
func (v *Vehicle) GetClimateUserPresets() error {
if !v.getRemoteOptionsStatus() {
v.client.logger.Error(APP_ERRORS["SUBSCRIBTION_REQUIRED"])

View File

@ -6,6 +6,7 @@ import (
"testing"
)
// TestGetClimateQuickPresets_Success tests the retrieval of quick climate presets.
func TestGetClimatePresets_Success(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
// Handle API_LOGIN endpoint
@ -32,7 +33,7 @@ func TestGetClimatePresets_Success(t *testing.T) {
ts.Start()
defer ts.Close()
cfg := makeConfig(t)
cfg := mockConfig(t)
msc, err := New(cfg)
if err != nil {
@ -49,6 +50,7 @@ func TestGetClimatePresets_Success(t *testing.T) {
}
}
// TestGetClimateQuickPresets_Success tests the retrieval of quick climate presets.
func TestGetClimateQuickPresets_Success(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
// Handle API_LOGIN endpoint
@ -75,7 +77,7 @@ func TestGetClimateQuickPresets_Success(t *testing.T) {
ts.Start()
defer ts.Close()
cfg := makeConfig(t)
cfg := mockConfig(t)
msc, err := New(cfg)
if err != nil {
@ -92,6 +94,7 @@ func TestGetClimateQuickPresets_Success(t *testing.T) {
}
}
// TestGetClimateUserPresets_Success tests the retrieval of user-defined climate presets.
func TestGetClimateUserPresets_Success(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
// Handle API_LOGIN endpoint
@ -118,7 +121,7 @@ func TestGetClimateUserPresets_Success(t *testing.T) {
ts.Start()
defer ts.Close()
cfg := makeConfig(t)
cfg := mockConfig(t)
msc, err := New(cfg)
if err != nil {
@ -135,6 +138,7 @@ func TestGetClimateUserPresets_Success(t *testing.T) {
}
}
// TestGetVehicleCondition_Success tests the GetVehicleCondition method
func TestGetVehicleStatus_Success(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
// Handle API_LOGIN endpoint
@ -161,7 +165,7 @@ func TestGetVehicleStatus_Success(t *testing.T) {
ts.Start()
defer ts.Close()
cfg := makeConfig(t)
cfg := mockConfig(t)
msc, err := New(cfg)
if err != nil {
@ -178,6 +182,7 @@ func TestGetVehicleStatus_Success(t *testing.T) {
}
}
// TestGetVehicleCondition_Success tests the GetVehicleCondition method
func TestGetVehicleCondition_Success(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
// Handle API_LOGIN endpoint
@ -204,7 +209,7 @@ func TestGetVehicleCondition_Success(t *testing.T) {
ts.Start()
defer ts.Close()
cfg := makeConfig(t)
cfg := mockConfig(t)
msc, err := New(cfg)
if err != nil {
@ -221,6 +226,7 @@ func TestGetVehicleCondition_Success(t *testing.T) {
}
}
// TestGetVehicleHealth_Success tests the successful retrieval of vehicle health data.
func TestGetVehicleHealth_Success(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
// Handle API_LOGIN endpoint
@ -247,7 +253,7 @@ func TestGetVehicleHealth_Success(t *testing.T) {
ts.Start()
defer ts.Close()
cfg := makeConfig(t)
cfg := mockConfig(t)
msc, err := New(cfg)
if err != nil {