Add depot, attendant, jetfire, optimus, ota services with kustomize overlays

This commit is contained in:
Chris Rai
2026-01-31 15:35:07 -05:00
parent a0ec642ca1
commit 9a5cb2f547
404 changed files with 38817 additions and 16 deletions

View File

@@ -0,0 +1,26 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/attendant/controllers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/pkg/errors"
)
func CarUpdateProgressStatus(db *services.DB, ka *services.KeepAwake, device common.Device, id string, data []byte) error {
logger.Debug().Msgf("CarUpdateProgressStatus %v %s", device, id)
clientPool := services.RedisClientPool()
handler := controllers.NewCarUpdateProgress(clientPool, ka, db, device)
if handler == nil {
return errors.Errorf("NewCarUpdateProgress cannot handle device %v", device)
}
defer handler.Dispose()
err := handler.Process(id, data)
return err
}

View File

@@ -0,0 +1,389 @@
package handlers_test
import (
"fmt"
"testing"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
)
func TestCarUpdateProgressFunctional(t *testing.T) {
t.Skip()
testVIN := "WBSEH93466B798124"
conn := tester.NewRedisMock()
db := services.GetDB()
manifest, carUpdateID, err := setupCarUpdateProgressFunc(db, testVIN)
if err != nil {
panic(err)
}
defer func() {
if carUpdateID > 0 {
db.GetCarUpdates().Delete(&common.CarUpdate{ID: carUpdateID})
conn.Delete(redis.CarUpdateStatusTBOXHashKey(carUpdateID), redis.CarUpdateStatusHMIHashKey(carUpdateID))
}
if manifest != nil && manifest.ID > 0 {
db.GetUpdateManifests().Delete(manifest)
}
}()
ka := services.NewKeepAwakeService()
ka.SetService(&services.MockKeepAwakeImplementation{})
type testCase struct {
Name string
Device common.Device
Payload string
ExpectedMsg string
ExpectInstalled int
ExpectInstallTotal int
ExpectDBStatus string
ExpectDownloadCurrent uint64
ExpectDownloadTotal uint64
ExpectErrorCode int
}
tests := []testCase{
{
Name: "manifest_received",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"msg": "manifest_received"
}`, carUpdateID),
ExpectedMsg: "manifest_received",
ExpectDBStatus: "manifest_received",
},
{
Name: "install_approval_await",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"msg": "install_approval_await"
}`, carUpdateID),
ExpectedMsg: "install_approval_await",
ExpectDBStatus: "install_approval_await",
},
{
Name: "other error",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 3,
"total_files": 10,
"msg": "other error",
"err": -100
}`, carUpdateID),
ExpectedMsg: "other error",
ExpectDBStatus: "other error",
ExpectInstalled: 3,
ExpectInstallTotal: 10,
ExpectErrorCode: -100,
},
{
Name: "download_start",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"file_current": 0,
"file_total": 100,
"package_current": 0,
"package_total": 100,
"msg": "download_start",
"err": 0
}`, carUpdateID),
ExpectedMsg: "downloading",
ExpectDBStatus: "package_download_start",
ExpectDownloadCurrent: 0,
ExpectDownloadTotal: 100,
},
{
Name: "downloading",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"file_current": 0,
"file_total": 100,
"package_current": 30,
"package_total": 100,
"msg": "downloading",
"err": 0
}`, carUpdateID),
ExpectedMsg: "downloading",
ExpectDBStatus: "package_download_start",
ExpectDownloadCurrent: 30,
ExpectDownloadTotal: 100,
},
{
Name: "download_complete",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"file_current": 100,
"file_total": 100,
"package_current": 900,
"package_total": 1000,
"msg": "download_complete",
"err": 0
}`, carUpdateID),
ExpectedMsg: "downloading",
ExpectDBStatus: "package_download_start",
ExpectDownloadCurrent: 900,
ExpectDownloadTotal: 1000,
},
{
Name: "package_download_complete",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"file_current": 100,
"file_total": 100,
"package_current": 1000,
"package_total": 1000,
"msg": "download_complete",
"err": 0
}`, carUpdateID),
ExpectedMsg: "package_download_complete",
ExpectDBStatus: "package_download_complete",
ExpectDownloadCurrent: 1000,
ExpectDownloadTotal: 1000,
},
{
Name: "download_error",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"file_current": 0,
"file_total": 100,
"package_current": 0,
"package_total": 1000,
"msg": "download_error",
"err": 0
}`, carUpdateID),
ExpectedMsg: "download_error",
ExpectDBStatus: "download_error",
ExpectDownloadCurrent: 0,
ExpectDownloadTotal: 1000,
},
{
Name: "install_start",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 0,
"total_files": 10,
"msg": "install_start",
"err": 0
}`, carUpdateID),
ExpectedMsg: "installing",
ExpectDBStatus: "package_install_start",
ExpectInstalled: 0,
ExpectInstallTotal: 10,
},
{
Name: "installing",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 2,
"total_files": 10,
"msg": "installing",
"err": 0
}`, carUpdateID),
ExpectedMsg: "installing",
ExpectDBStatus: "package_install_start",
ExpectInstalled: 2,
ExpectInstallTotal: 10,
},
{
Name: "install_complete",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 9,
"total_files": 10,
"msg": "install_complete",
"err": 0
}`, carUpdateID),
ExpectedMsg: "installing",
ExpectDBStatus: "package_install_start",
ExpectInstalled: 9,
ExpectInstallTotal: 10,
},
{
Name: "package_install_complete",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 10,
"total_files": 10,
"msg": "install_complete",
"err": 0
}`, carUpdateID),
ExpectedMsg: "package_install_complete",
ExpectDBStatus: "package_install_complete",
ExpectInstalled: 10,
ExpectInstallTotal: 10,
},
{
Name: "install_error",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 3,
"total_files": 10,
"msg": "install_error",
"err": 0
}`, carUpdateID),
ExpectedMsg: "install_error",
ExpectDBStatus: "install_error",
ExpectInstalled: 3,
ExpectInstallTotal: 10,
},
{
Name: "package_download_complete",
Device: common.HMI,
Payload: fmt.Sprintf(`{
"car_update_id":%d,
"ecu":"ICC",
"file_current":null,
"file_total":null,
"package_current":920639485,
"package_total":920639485,
"installed":null,
"total_files":null,
"msg":"package_download_complete",
"err":null
}`, carUpdateID),
ExpectedMsg: "package_download_complete",
ExpectDBStatus: "package_download_complete",
ExpectInstalled: 3,
ExpectInstallTotal: 10,
ExpectDownloadCurrent: 920639485,
ExpectDownloadTotal: 920639485,
},
}
keys := make([]string, 1)
statuses := make([]interface{}, 1)
for _, test := range tests {
err := handlers.CarUpdateProgressStatus(db, ka, test.Device, testVIN, []byte(test.Payload))
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s output error", test.Device, test.Name), nil, err)
}
status := common.CarUpdateProgress{}
keys[0] = redis.CarUpdateStatusHashKey(carUpdateID)
statuses[0] = &status
err = conn.GetObjectsMulti(keys, statuses)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "GetObjectsMulti", nil, err)
return
}
if status.Status != test.ExpectedMsg {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s Status", test.Device, test.Name), test.ExpectedMsg, status.Status)
}
if status.InstalledFiles != test.ExpectInstalled {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s InstalledFiles", test.Device, test.Name), test.ExpectInstalled, status.InstalledFiles)
}
if status.TotalFiles != test.ExpectInstallTotal {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s TotalFiles", test.Device, test.Name), test.ExpectInstallTotal, status.TotalFiles)
}
if status.PackageCurrent != test.ExpectDownloadCurrent {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s PackageCurrent", test.Device, test.Name), test.ExpectDownloadCurrent, status.PackageCurrent)
}
if status.PackageTotal != test.ExpectDownloadTotal {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s PackageTotal", test.Device, test.Name), test.ExpectDownloadTotal, status.PackageTotal)
}
if status.ErrorCode != test.ExpectErrorCode {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s ErrorCode", test.Device, test.Name), test.ExpectErrorCode, status.ErrorCode)
}
cu, err := db.GetCarUpdates().SelectByID(carUpdateID)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "Get from DB", nil, err)
return
}
if cu.Status != test.ExpectDBStatus {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s DB Status", test.Device, test.Name), test.ExpectDBStatus, cu.Status)
}
if cu.ErrorCode != test.ExpectErrorCode {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s DB ErrorCode", test.Device, test.Name), test.ExpectErrorCode, cu.ErrorCode)
}
}
}
func setupCarUpdateProgressFunc(db *services.DB, vin string) (*common.UpdateManifest, int64, error) {
_, err := db.GetCars().SelectOrInsert(&common.Car{
VIN: vin,
Model: "Ocean",
Year: 2022,
Trim: "Sport",
})
if err != nil {
return nil, 0, err
}
manifest := common.UpdateManifest{
Name: "TestCarUpdateProgressFunctional",
Version: "1000",
Description: "For TestCarUpdateProgressFunctional",
ReleaseNotes: "http://releasenotes.com",
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
}
_, err = db.GetUpdateManifests().Insert(&manifest)
if err != nil {
return nil, 0, err
}
_, err = db.GetUpdateManifests().ECUInsert(&common.UpdateManifestECU{
UpdateManifestID: manifest.ID,
ECU: "ICC",
Version: "ICCVERSION",
})
if err != nil {
return nil, 0, err
}
_, err = db.GetUpdateManifests().ECUInsert(&common.UpdateManifestECU{
UpdateManifestID: manifest.ID,
ECU: "ADAS",
Version: "ADASVERSION",
})
if err != nil {
return nil, 0, err
}
carupdate := common.CarUpdate{
VIN: vin,
UpdateManifestID: manifest.ID,
}
_, err = db.GetCarUpdates().Insert(&carupdate)
if err != nil {
return nil, 0, err
}
return &manifest, carupdate.ID, nil
}

View File

@@ -0,0 +1,776 @@
package handlers_test
import (
"errors"
"fmt"
"net/http"
"strings"
"testing"
"time"
"github.com/fiskerinc/cloud-services/services/attendant/controllers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/common/manifestfingerprintparams"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
)
var (
schemaToTRex = "file://" + th.GetSchemaDirPath() + "/trex/RXMessage.json"
schemaToHMI = "file://" + th.GetSchemaDirPath() + "/hmi/RXMessage.json"
)
func TestCarUpdateProgress(t *testing.T) {
testGetSetResult := `["valid-cognito-id-1","valid-cognito-id-2"]`
testVIN := "JH4KA7680RC01"
mobile1Key := "3:valid-cognito-id-1"
mobile2Key := "3:valid-cognito-id-2"
hmiKey := "2:JH4KA7680RC01"
trexKey := common.TRex.Key(testVIN)
carupdateKey := "carupdate:297"
var bhex common.BinaryHex
expectedExpire := 3600
bhex = []byte("test")
fingerprintTime, _ := time.Parse("02/01/06", "19/01/24")
fpp := manifestfingerprintparams.MockFingerprintParamer{
ManifestSerialValue: "00000000000000000",
Time: fingerprintTime,
}
manifestfingerprintparams.SetFPParams(&fpp)
manifest := common.UpdateManifest{
ID: 1,
Name: "test",
Version: "MANIFEST_VERSION",
SUMS: "2023.10.01.00.E",
Description: "description",
ReleaseNotes: "http://releasenotes.com",
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
RollbackEnabled: true,
Type: "standard",
ECUs: []*common.UpdateManifestECU{
{
ECU: "ICC",
Version: "version",
HWVersions: []string{"hardware_version"},
Mode: "D",
SelfDownload: true,
Files: []*common.UpdateManifestFile{
{
FileID: "fileid",
URL: "http://download.com",
Filename: "filename.bin",
FileSize: 10000,
FileType: common.Software,
WriteRegionID: 2222,
WriteRegion: common.MemoryRegion{
ID: 2000,
Offset: 10000,
Length: 20,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
{
ECU: "ADAS",
Version: "version",
HWVersions: []string{"hardware_version"},
Mode: "A",
InstallPriority: 10,
Files: []*common.UpdateManifestFile{
{
FileID: "fileid",
URL: "http://download.com",
Filename: "adas.bin",
FileSize: 9999,
FileType: common.Software,
WriteRegionID: 9999,
WriteRegion: common.MemoryRegion{
ID: 8888,
Offset: 8888,
Length: 8888,
},
DBModelBase: th.Timestamp,
},
},
ECCKeys: &common.ECCKeys{
ECU: "ADAS",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
},
DBModelBase: th.Timestamp,
},
{
ECU: "ECUA",
Version: "version",
HWVersions: []string{"hardware_version"},
Mode: "A",
InstallPriority: 5,
Files: []*common.UpdateManifestFile{
{
FileID: "fileid",
URL: "http://download.com",
Filename: "adas.bin",
FileSize: 9999,
FileType: common.Software,
WriteRegionID: 9999,
WriteRegion: common.MemoryRegion{
ID: 8888,
Offset: 8888,
Length: 8888,
},
DBModelBase: th.Timestamp,
},
{
FileID: "SHOULD_NOT_BE_IN_UPDATE",
URL: "http://download.com/SHOULD_NOT_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: elptr.ElPtr(false),
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
DBModelBase: th.Timestamp,
},
{
FileID: "MUST_BE_IN_UPDATE",
URL: "http://download.com/MUST_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: elptr.ElPtr(true),
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
DBModelBase: th.Timestamp,
},
},
ECCKeys: &common.ECCKeys{
ECU: "ECUA",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
}
ecuaRollback := []*common.UpdateManifestECU{
{
ID: 100,
UpdateManifestID: 200,
ECU: "ECUA",
Version: "VERSIONOLD",
HWVersions: []string{"hardware_version"},
Mode: "A",
DBModelBase: th.Timestamp,
Files: []*common.UpdateManifestFile{
{
FileID: "FILEIDOLD",
UpdateManifestECUID: 1001,
Filename: "FILENAMEOLD",
URL: "URLOLD",
FileType: common.Software,
WriteRegionID: 700,
WriteRegion: common.MemoryRegion{
Offset: 701,
Length: 702,
},
FileSize: 240,
DBModelBase: th.Timestamp,
},
},
ECCKeys: &common.ECCKeys{
ECU: "ECUA",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
},
},
}
mockDB := &services.DB{}
mockCars := &mocks.MockCars{}
mockCarUpdates := &mocks.MockCarUpdates{
SelectCarUpdateResponse: &common.CarUpdate{
UpdateManifestID: 816,
UpdateManifest: &common.UpdateManifest{
ID: 816,
},
},
}
mockManifests := &mocks.MockUpdateManifests{
ECUUpdatesMock: func(man *common.UpdateManifestECU, vin string) ([]*common.UpdateManifestECU, error) {
if man.ECU == "ECUA" {
return ecuaRollback, nil
}
return nil, nil
},
}
mockKeys := &mocks.MockEccKeys{
MockListResponse: []common.ECCKeys{
{
ECU: "PDU",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
},
{
ECU: "TBOX",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
},
},
}
mockDB.SetCars(mockCars)
mockDB.SetCarUpdates(mockCarUpdates)
mockDB.SetECCKeys(mockKeys)
mockDB.SetManifests(mockManifests)
mockFoa := FoaServiceMock{}
services.SetFoaService(&mockFoa)
mockRedis := tester.NewRedisMock()
mockKeepAwake := services.NewKeepAwakeService()
services.SetRedisClientPool(tester.NewMockClientPool(mockRedis))
mockSap := vconfig.SAPServiceMock{GetConfigurationMock: func(vin string) (common.SAPResponse, error) {
return common.SAPResponse{
ModelYear: 2023,
ModelType: "Ocean",
VersionDuringModelYear: "1",
Features: []common.SAPFeature{
{
FamilyCode: "FamilyCode1",
FeatureCode: "FeatureCode1",
},
{
FamilyCode: "FamilyCode2",
FeatureCode: "FeatureCode2",
},
},
}, nil
}}
mockConf := vconfig.ConfigMock{GetVODCDSCodingDataMock: func(request common.VODCDSRequest) (map[string]string, error) {
return map[string]string{
"ECUA": "config",
"VOD": "00a62299027600000101012200010100010001010101000000000000000000fffeffff000101010101010101010100010001010101000101010100000000000000000000000000010101000100000100010101000201010101000101020101000101010200010101010101010101000101010100010001010101010101010201010000000000000100000101ff00000001010200000000000003ffffffff0000000201010200000100000000000000000000000000000000000000000000000000000000000000000001202310010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, nil
}}
services.SetSapService(mockSap)
services.SetVehicleConfig(mockConf)
schemaTesterHMI := th.NewSchemaTestHelper(t, schemaToHMI)
schemaTesterTRex := th.NewSchemaTestHelper(t, schemaToTRex)
tests := []AttendentRouteTestCase{
{
Name: "[HMI] install_error",
RedisTestCase: tester.RedisTestCase{
Device: common.HMI,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":5,"total_files":10,"msg":"install_error","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":5,"status":"install_failed","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[HMI] download_completed",
RedisTestCase: tester.RedisTestCase{
Device: common.HMI,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"ICC","file_current":null,"file_total":null,"package_current":920639485,"package_total":920639485,"installed":null,"total_files":null,"msg":"download_completed","err":null}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":920639485,"ecu":"ICC","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"package_download_complete","total_files":0,"total_size":920639485}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
trexKey: `{"handler":"update_manifest","data":{"ecu_updates":[{"name":"ICC","version":"version","hw_version":"hardware_version","self_download":true},{"name":"ECUA","version":"version","hw_version":"hardware_version","configuration":"config","files":[{"file_id":"fileid","url":"http://download.com","file_size":9999,"type":"software","write_region":{"offset":8888,"length":8888}},{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}}],"rollback":[{"version":"VERSIONOLD","files":[{"file_id":"FILEIDOLD","url":"URLOLD","file_size":240,"type":"software","write_region":{"offset":701,"length":702}}]}],"ecc_keys":{"level_1":"74657374","level_2":"74657374","level_3":"74657374"}},{"name":"ADAS","version":"version","hw_version":"hardware_version","files":[{"file_id":"fileid","url":"http://download.com","file_size":9999,"type":"software","write_region":{"offset":8888,"length":8888}}],"ecc_keys":{"level_1":"74657374","level_2":"74657374","level_3":"74657374"}}],"fingerprint":"240119FISKER00000000000000000","car_update_id":297,"rollback":true,"type":"standard","vod":"01012299027600000101012200010100010001010101000000000000000000fffeffff000101010101010101010100010001010101000101010100000000000000000000000000010101000100000100010101000201010101000101020101000101010200010101010101010101000101010100010001010101010101010201010000000000000100000101ff00000001010200000000000003ffffffff00000002010102000001000000000000000000000000000000000000000000000000000000000000000000012023100100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021","update_duration":30}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"package_download_complete","err":0}}`,
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"package_download_complete","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"package_download_complete","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
MockLoadManifest: &manifest,
},
{
Name: "[HMI] manifest_succeeded",
RedisTestCase: tester.RedisTestCase{
Device: common.HMI,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"ICC","file_current":null,"file_total":null,"package_current":920639485,"package_total":920639485,"installed":null,"total_files":null,"msg":"manifest_succeeded","err":null}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":920639485,"ecu":"ICC","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"manifest_succeeded","total_files":0,"total_size":920639485}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"manifest_succeeded","err":0}}`,
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"manifest_succeeded","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"manifest_succeeded","err":0}}`,
"1:JH4KA7680RC01": `{"handler":"read_ecu_versions","data":{"ecu_name":"*"}}`,
},
MockRedisGetSet: testGetSetResult,
},
SelectCarUpdate: &common.CarUpdate{
UpdateManifest: &validUpdateManifest,
},
},
{
Name: "[TREX] manifest_received",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"msg":"manifest_received","err":-6,"extra_info":""}`,
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_received","err":-6}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_received","err":-6}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_received","err":-6}}`,
},
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"","errorcode":-6,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"manifest_received","total_files":0,"total_size":0}`,
Expires: expectedExpire,
},
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] manifest_accepted",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"msg":"manifest_accepted","err":-7,"extra_info":""}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"","errorcode":-7,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"manifest_accepted","total_files":0,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_accepted","err":-7}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_accepted","err":-7}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_accepted","err":-7}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] download_started",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"msg":"download_started","err":-14,"extra_info":""}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"","errorcode":-14,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"downloading","total_files":0,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"downloading","err":-14}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"downloading","err":-14}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"downloading","err":-14}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] downloading",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"ADAS","file_current":1048576,"file_total":1264672,"package_current":1048576,"package_total":2529856,"msg":"downloading","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":1048576,"ecu":"ADAS","errorcode":0,"file_size":1048576,"file_total":1264672,"id":297,"installed":0,"status":"downloading","total_files":0,"total_size":2529856}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1048576,"file_total":1264672,"package_current":1048576,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1048576,"file_total":1264672,"package_current":1048576,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":1048576,"file_total":1264672,"package_current":1048576,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] download_completed ECU 1",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"ADAS","file_current":1264672,"file_total":1264672,"package_current":1264672,"package_total":2529856,"msg":"download_completed","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":1264672,"ecu":"ADAS","errorcode":0,"file_size":1264672,"file_total":1264672,"id":297,"installed":0,"status":"downloading","total_files":0,"total_size":2529856}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1264672,"file_total":1264672,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1264672,"file_total":1264672,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":1264672,"file_total":1264672,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] download_started ECU 2",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"EKS","file_current":0,"file_total":1265184,"package_current":1264672,"package_total":2529856,"msg":"download_started","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":1264672,"ecu":"EKS","errorcode":0,"file_size":0,"file_total":1265184,"id":297,"installed":0,"status":"downloading","total_files":0,"total_size":2529856}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":1265184,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":1265184,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":1265184,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] downloading ECU 2",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"EKS","file_current":1048576,"file_total":1265184,"package_current":2313248,"package_total":2529856,"msg":"downloading","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":2313248,"ecu":"EKS","errorcode":0,"file_size":1048576,"file_total":1265184,"id":297,"installed":0,"status":"downloading","total_files":0,"total_size":2529856}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1048576,"file_total":1265184,"package_current":2313248,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1048576,"file_total":1265184,"package_current":2313248,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":1048576,"file_total":1265184,"package_current":2313248,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] download_completed ECU 2",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"EKS","file_current":1265184,"file_total":1265184,"package_current":2529856,"package_total":2529856,"msg":"download_completed","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":2529856,"ecu":"EKS","errorcode":0,"file_size":1265184,"file_total":1265184,"id":297,"installed":0,"status":"package_download_complete","total_files":0,"total_size":2529856}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1265184,"file_total":1265184,"package_current":2529856,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"package_download_complete","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1265184,"file_total":1265184,"package_current":2529856,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"package_download_complete","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":1265184,"file_total":1265184,"package_current":2529856,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"package_download_complete","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] package_download_complete",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"msg":"download_completed","err":-15,"extra_info":""}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"","errorcode":-15,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"package_download_complete","total_files":0,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"package_download_complete","err":-15}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"package_download_complete","err":-15}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"package_download_complete","err":-15}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] download_failed",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","file_current":0,"file_total":100,"package_current":0,"package_total":1000,"msg":"download_failed","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":100,"id":297,"installed":0,"status":"download_failed","total_files":0,"total_size":1000}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":100,"package_current":0,"package_total":1000,"installed":0,"total_files":0,"car_update_id":297,"ecu":"TEST","msg":"download_failed","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":100,"package_current":0,"package_total":1000,"installed":0,"total_files":0,"car_update_id":297,"ecu":"TEST","msg":"download_failed","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":100,"package_current":0,"package_total":1000,"installed":0,"total_files":0,"car_update_id":297,"ecu":"TEST","msg":"download_failed","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] install_started",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":0,"total_files":10,"msg":"install_started","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"installing","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] installing",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":5,"total_files":10,"msg":"installing","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":5,"status":"installing","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] install_succeeded ECU",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":10,"total_files":10,"msg":"install_succeeded"}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":10,"status":"package_install_complete","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"package_install_complete","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"package_install_complete","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"package_install_complete","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] package_install_complete",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"msg":"install_succeeded","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"installing","total_files":0,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"installing","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"installing","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"installing","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] install_failed",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":5,"total_files":10,"msg":"install_failed","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":5,"status":"install_failed","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] requirements_failed",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":5,"total_files":10,"msg":"requirements_failed","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":5,"status":"requirements_failed","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"requirements_failed","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"requirements_failed","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"requirements_failed","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] install_scheduled ECU",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":10,"total_files":10,"msg":"install_scheduled"}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":10,"status":"install_scheduled","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_scheduled","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_scheduled","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_scheduled","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] manifest_succeeded",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":10,"total_files":10,"msg":"manifest_succeeded"}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":10,"status":"manifest_succeeded","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"manifest_succeeded","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"manifest_succeeded","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"manifest_succeeded","err":0}}`,
"1:JH4KA7680RC01": `{"handler":"read_ecu_versions","data":{"ecu_name":"*"}}`,
},
MockRedisGetSet: testGetSetResult,
},
SelectCarUpdate: &common.CarUpdate{
UpdateManifest: &validUpdateManifest,
},
},
}
for i := range tests {
mockRedis.Reset()
test := &tests[i]
test.SetupRedis(mockRedis)
test.SetupDB(mockCars, mockCarUpdates, test)
redisPool := tester.NewMockClientPool(mockRedis)
handler := controllers.NewCarUpdateProgress(redisPool, mockKeepAwake, mockDB, test.Device)
if handler == nil {
t.Error(errors.New("NewCarUpdateProgress cannot handle device %v"))
continue
}
err := handler.Process(test.DeviceKey, []byte(test.PayloadData))
test.CheckHandlerError(t, test.Name, err)
test.Validate(t, test.Name, mockRedis)
for key, m := range test.RedisTestCase.ExpectedMessages {
name := fmt.Sprintf("%s %s", test.Name, key)
if strings.Contains(key, "1:") {
schemaTesterTRex.ValidateSchemaObject(name, []byte(m))
} else if strings.Contains(key, "2:") {
schemaTesterHMI.ValidateSchemaObject(name, []byte(m))
}
}
}
}
type FoaServiceMock struct{}
func (f *FoaServiceMock) OtaUpdateStatus(vin string, carUpdate *common.CarUpdate, status *common.CarUpdateProgress) (*http.Response, error) {
return &http.Response{StatusCode: 200}, nil
}

View File

@@ -0,0 +1,74 @@
package handlers
import (
"encoding/json"
"errors"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
fv "github.com/fiskerinc/cloud-services/pkg/flashpackversion"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/validator"
)
func UpdateCarState(db *services.DB, vin string, data []byte) error {
logger.Debug().Msgf("UpdateCarState %s", vin)
var update common.CarStateUpdate
err := json.Unmarshal(data, &update)
if err != nil {
return err
}
err = validator.ValidateStruct(&update)
if err != nil {
logger.Err(err).Interface("CarStateUpdate", update).Str("CarStateUpdateByteValue as string", string(data)).Send()
return err
}
return processUpdateCarStateECUs(db, vin, update.ECUs)
}
func processUpdateCarStateECUs(db *services.DB, vin string, ecus map[string]common.CarECU) error {
cache := services.GetCarEcuCache()
insert := []common.CarECU{}
errs := []error{}
for name, ecu := range ecus {
ecu.VIN = vin
ecu.ECU = name
err := validator.ValidateStruct(&ecu)
if err != nil {
logger.Error().Msgf("invalid CarECU %v", err)
errs = append(errs, err)
continue
}
if !cache.Exists(ecu.CacheKey(), ecu.HashValues()) {
insert = append(insert, ecu)
}
}
if len(insert) == 0 {
return combineErrors(errs)
}
err := fv.InsertCarECUsAndUpdateFlashpackVersion(db.GetCars(), db.GetCarVersionsLog(), vin, insert)
if err != nil {
errs = append(errs, err)
}
return combineErrors(errs)
}
func combineErrors(errs []error) error {
if len(errs) == 0 {
return nil
}
errString := errs[0].Error()
for i := 1; i < len(errs); i++ {
errString += " " + errs[i].Error()
}
return errors.New(errString)
}

View File

@@ -0,0 +1,113 @@
package handlers_test
import (
"testing"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
)
func TestUpdateCarState(t *testing.T) {
setupDBMock()
setupRedisMock()
mockSap := vconfig.SAPServiceMock{}
services.SetSapService(mockSap)
type testCase struct {
Name string
VIN string
Payload string
ExpectedErr string
}
tests := []testCase{
{
Name: "Empty ECU",
VIN: "JH4KA7680RC011845",
Payload: `{"ecus": {}}`,
ExpectedErr: "Key: 'CarStateUpdate.ECUs' Error:Field validation for 'ECUs' failed on the 'min' tag",
},
{
Name: "Bad VIN and ECU",
VIN: "JH4KA7680RC01",
Payload: `{
"ecus": {
"ECU1": {
"serial_number": "AAAAA",
"hw_version": "2000",
"boot_loader_version": "3000",
"fingerprint": "0xffffffffffffffffff",
"config": "0x024000941f9fffbfffe2dd9bff5860000007dfff091fff7f",
"vendor": "A021E00029"
}
}
}`,
ExpectedErr: "Key: 'CarECU.VIN' Error:Field validation for 'VIN' failed on the 'vin' tag",
},
{
Name: "Good ECUs with/without software version",
VIN: "JH4KA7680RC011845",
Payload: `{
"ecus": {
"ECU1": {
"sw_version": "1000",
"serial_number": "AAAAA",
"hw_version": "2000",
"boot_loader_version": "3000",
"fingerprint": "0xffffffffffffffffff",
"config": "0x024000941f9fffbfffe2dd9bff5860000007dfff091fff7f",
"vendor": "A021E00029"
},
"ECU2": {
"serial_number": "BBBBBBB",
"hw_version": "2001",
"boot_loader_version": "3001",
"fingerprint": "0xffffffffffffffffff",
"config": "0x024000941f9fffbfffe2dd9bff5860000007dfff091fff7f",
"vendor": "A021E00029"
}
}
}`,
ExpectedErr: "",
},
{
Name: "bad ECU followed by a Good ECU",
VIN: "JH4KA7680RC011845",
Payload: `{
"ecus": {
"ECU1": {
"serial_number": "AAAAA",
"hw_version": "2000",
"boot_loader_version": "3000",
"fingerprint": "0xffffffffffffffffff",
"config": "0x024000941f9fffbfffe2dd9bff5860000007dfff091fff7f",
"vendor": "A021E00029",
"epoch_usec": "ABC"
},
"ECU2": {
"serial_number": "BBBBBBB",
"hw_version": "2001",
"boot_loader_version": "3001",
"fingerprint": "0xffffffffffffffffff",
"config": "0x024000941f9fffbfffe2dd9bff5860000007dfff091fff7f",
"vendor": "A021E00029"
}
}
}`,
ExpectedErr: "json: cannot unmarshal string into Go struct field CarECU.ecus.epoch_usec of type int64",
},
}
for _, test := range tests {
err := handlers.UpdateCarState(mockDB, test.VIN, []byte(test.Payload))
if err != nil && test.ExpectedErr != err.Error() {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.ExpectedErr, err.Error())
} else if err == nil && test.ExpectedErr != "" {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.ExpectedErr, err)
}
}
}

View File

@@ -0,0 +1,94 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/attendant/services"
"encoding/json"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/pkg/errors"
)
func GetAllEccKeys(db *services.DB, id string, data []byte) error {
logger.Debug().Msgf("GetAllEccKeys %v %s", common.TRex, id)
var err error
var eccKeys []common.ECCKeys
req, err := parseGetAllEccKeysRequest(data)
if err != nil {
return err
}
if req.CarUpdateID == 0 {
eccKeys, err = db.GetECCKeys().SelectAllPrivateKeysByVIN(id)
} else {
eccKeys, err = db.GetECCKeys().SelectAllPrivateKeysByCarUpdateID(req.CarUpdateID)
}
if err != nil {
return err
}
if eccKeys == nil {
eccKeys = make([]common.ECCKeys, 0)
}
eccKeys = replaceECU(eccKeys)
client := services.RedisClientPool().GetFromPool()
defer client.Close()
err = client.SafePublishMessage(common.TRex.Key(id), common.Message{
Handler: "ecc_keys",
Data: eccKeys,
})
if err != nil {
return err
}
logger.Debug().Msgf("GetAllEccKeys sent %v %s", common.TRex, id)
return nil
}
func parseGetAllEccKeysRequest(data []byte) (common.CarUpdateRequest, error) {
var req common.CarUpdateRequest
if len(data) == 0 {
return req, nil
}
err := json.Unmarshal(data, &req)
if err != nil {
return req, errors.WithStack(err)
}
return req, nil
}
func replaceECU(eccKeys []common.ECCKeys) []common.ECCKeys {
ecuReplacements := common.ECUReplacement()
for i := 0; i < len(eccKeys); i++ {
ecu := eccKeys[i].ECU
if replacement, exist := ecuReplacements[ecu]; exist {
eccKeys[i].ECU = replacement
}
}
return eccKeys
}
/*
func notifyEccKeysGeneralError(client redis.Client, device common.Device, id string, err error) {
e := client.PublishMessage(device.Key(id), common.Message{
Handler: "ecc_keys",
Data: []struct{ Error string }{
{
Error: err.Error(),
},
},
})
if e != nil {
logger.Error().Err(errors.WithStack(e)).Send()
}
}
*/

View File

@@ -0,0 +1,111 @@
package handlers_test
import (
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"testing"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
"github.com/pkg/errors"
)
func TestGetAllEccKeys(t *testing.T) {
testVIN := "JH4KA7680RC01"
trexKey := "1:JH4KA7680RC01"
pk11 := common.NewBinaryHex([]byte("testprivkey11"))
pk12 := common.NewBinaryHex([]byte("testprivkey12"))
pk13 := common.NewBinaryHex([]byte("testprivkey13"))
pk21 := common.NewBinaryHex([]byte("testprivkey21"))
pk22 := common.NewBinaryHex([]byte("testprivkey22"))
pk23 := common.NewBinaryHex([]byte("testprivkey23"))
testDBQuery := []common.ECCKeys{
{
ECU: "testecu1",
PrivKey1: &pk11,
PrivKey2: &pk12,
PrivKey3: &pk13,
},
{
ECU: "testecu2",
PrivKey1: &pk21,
PrivKey2: &pk22,
PrivKey3: &pk23,
},
{
ECU: "PDU",
PrivKey1: &pk21,
PrivKey2: &pk22,
PrivKey3: &pk23,
},
}
mockRedis := tester.NewRedisMock()
services.SetRedisClientPool(tester.NewMockClientPool(mockRedis))
mockEccKeys := &mocks.MockEccKeys{}
mockDB := &services.DB{}
mockDB.SetECCKeys(mockEccKeys)
tests := []AttendentRouteTestCase{
{
Name: "[TREX] From DB, no car update id",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
ExpectedMessages: map[string]string{
trexKey: `{"handler":"ecc_keys","data":[{"ecu":"testecu1","level_1":"74657374707269766b65793131","level_2":"74657374707269766b65793132","level_3":"74657374707269766b65793133"},{"ecu":"testecu2","level_1":"74657374707269766b65793231","level_2":"74657374707269766b65793232","level_3":"74657374707269766b65793233"},{"ecu":"OBC","level_1":"74657374707269766b65793231","level_2":"74657374707269766b65793232","level_3":"74657374707269766b65793233"}]}`,
},
},
MockEccKeysSelect: testDBQuery,
},
{
Name: "[TREX] From DB, with car update id",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
ExpectedMessages: map[string]string{
trexKey: `{"handler":"ecc_keys","data":[{"ecu":"testecu1","level_1":"74657374707269766b65793131","level_2":"74657374707269766b65793132","level_3":"74657374707269766b65793133"},{"ecu":"testecu2","level_1":"74657374707269766b65793231","level_2":"74657374707269766b65793232","level_3":"74657374707269766b65793233"},{"ecu":"OBC","level_1":"74657374707269766b65793231","level_2":"74657374707269766b65793232","level_3":"74657374707269766b65793233"}]}`,
},
PayloadData: `{"car_update_id":10000}`,
},
MockEccKeysSelect: testDBQuery,
},
{
Name: "Error",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
ExpectedError: "something went wrong",
},
MockEccKeysSelect: nil,
},
}
schemaTester := testhelper.NewSchemaTestHelper(t, schemaToTRex)
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
mockRedis.Reset()
test.SetupRedis(mockRedis)
mockEccKeys.MockListResponse = test.MockEccKeysSelect
if test.Name == "Error" {
mockEccKeys.Error = errors.New("something went wrong")
} else {
mockEccKeys.Error = nil
}
err := handlers.GetAllEccKeys(mockDB, test.DeviceKey, []byte(test.PayloadData))
test.CheckHandlerError(t, test.Name, err)
test.Validate(t, test.Name, mockRedis)
for _, m := range test.ExpectedMessages {
schemaTester.ValidateSchemaObject(test.Name, []byte(m))
}
})
}
}

View File

@@ -0,0 +1,80 @@
package handlers
import (
"encoding/json"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/cache"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/fiskerinc/cloud-services/pkg/validator"
"github.com/pkg/errors"
)
func GetFileKeys(db *services.DB, device common.Device, id string, data []byte) error {
logger.Debug().Msgf("GetFileKeys %v %s", device, id)
var err error
var req *common.FileKeysRequest
client := services.RedisClientPool().GetFromPool()
defer client.Close()
req, err = parseGetFileKeysRequest(data)
if err != nil {
notifyFileKeysGeneralError(client, device, id, err)
return err
}
keys, err := cache.RetrieveFileEncryptionParams(client, db.GetFileKeys(), req.FileIDs)
if err != nil {
notifyFileKeysGeneralError(client, device, id, err)
return err
}
err = client.SafeQueueMessage(device.Key(id), common.Message{
Handler: "filekeys",
Data: keys,
})
if err != nil {
return err
}
logger.Debug().Msgf("GetFileKeys sent %v %s", device, id)
return nil
}
func parseGetFileKeysRequest(data []byte) (*common.FileKeysRequest, error) {
var status common.FileKeysRequest
err := json.Unmarshal(data, &status)
if err != nil {
return nil, errors.WithStack(err)
}
err = validator.ValidateStruct(status)
if err != nil {
return &status, errors.WithStack(err)
}
return &status, nil
}
func notifyFileKeysGeneralError(client redis.Client, device common.Device, id string, err error) {
e := client.SafePublishMessage(device.Key(id), common.Message{
Handler: "filekeys",
Data: []common.FileKeyResponse{
{
FileID: "0",
Error: err.Error(),
},
},
})
if e != nil {
logger.Error().Err(errors.WithStack(e)).Send()
}
}

View File

@@ -0,0 +1,124 @@
package handlers_test
import (
"testing"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
)
func TestGetFileKey(t *testing.T) {
testVIN := "JH4KA7680RC01"
trexKey := "1:JH4KA7680RC01"
hmiKey := "2:JH4KA7680RC01"
fileCache1 := "fileid:b7d94be8c94062cf"
fileCache2 := "fileid:83165a80c940e8b3"
testPayload := `{"file_ids": ["b7d94be8c94062cf","83165a80c940e8b3"]}`
testDBQuery := []common.FileKey{
{
FileID: "b7d94be8c94062cf",
Auth: []byte("AuthValue"),
Key: []byte("KeyValue"),
Nonce: []byte("NonceValue"),
},
{
FileID: "83165a80c940e8b3",
Auth: []byte("AuthValue2"),
Key: []byte("KeyValue2"),
Nonce: []byte("NonceValue2"),
},
}
mockRedis := tester.NewRedisMock()
services.SetRedisClientPool(tester.NewMockClientPool(mockRedis))
mockFileKeys := &mocks.MockFileKeys{}
mockDB := &services.DB{}
mockDB.SetFileKeys(mockFileKeys)
tests := []AttendentRouteTestCase{
{
Name: "[TREX] From DB",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: testPayload,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
fileCache1: {
Value: `{"file_id":"b7d94be8c94062cf","key":"S2V5VmFsdWU=","auth":"QXV0aFZhbHVl","nonce":"Tm9uY2VWYWx1ZQ=="}`,
Expires: 86400,
},
fileCache2: {
Value: `{"file_id":"83165a80c940e8b3","key":"S2V5VmFsdWUy","auth":"QXV0aFZhbHVlMg==","nonce":"Tm9uY2VWYWx1ZTI="}`,
Expires: 86400,
},
},
ExpectedMessages: map[string]string{
trexKey: `{"handler":"filekeys","data":[{"file_id":"b7d94be8c94062cf","key":"S2V5VmFsdWU=","auth":"QXV0aFZhbHVl","nonce":"Tm9uY2VWYWx1ZQ=="},{"file_id":"83165a80c940e8b3","key":"S2V5VmFsdWUy","auth":"QXV0aFZhbHVlMg==","nonce":"Tm9uY2VWYWx1ZTI="}]}`,
},
},
MockFileKeysSelect: testDBQuery,
MockFileKeysError: nil,
},
{
Name: "[HMI] From DB",
RedisTestCase: tester.RedisTestCase{
Device: common.HMI,
DeviceKey: testVIN,
PayloadData: testPayload,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
fileCache1: {
Value: `{"file_id":"b7d94be8c94062cf","key":"S2V5VmFsdWU=","auth":"QXV0aFZhbHVl","nonce":"Tm9uY2VWYWx1ZQ=="}`,
Expires: 86400,
},
fileCache2: {
Value: `{"file_id":"83165a80c940e8b3","key":"S2V5VmFsdWUy","auth":"QXV0aFZhbHVlMg==","nonce":"Tm9uY2VWYWx1ZTI="}`,
Expires: 86400,
},
},
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"filekeys","data":[{"file_id":"b7d94be8c94062cf","key":"S2V5VmFsdWU=","auth":"QXV0aFZhbHVl","nonce":"Tm9uY2VWYWx1ZQ=="},{"file_id":"83165a80c940e8b3","key":"S2V5VmFsdWUy","auth":"QXV0aFZhbHVlMg==","nonce":"Tm9uY2VWYWx1ZTI="}]}`,
},
},
MockFileKeysSelect: testDBQuery,
MockFileKeysError: nil,
},
}
schemaTester := testhelper.NewSchemaTestHelper(t, schemaToTRex)
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
mockRedis.Reset()
test.SetupRedis(mockRedis)
mockFileKeys.GetMultiResponse = test.MockFileKeysSelect
mockFileKeys.Error = test.MockFileKeysError
err := handlers.GetFileKeys(mockDB, test.Device, test.DeviceKey, []byte(test.PayloadData))
test.CheckHandlerError(t, test.Name, err)
test.Validate(t, test.Name, mockRedis)
for _, m := range test.ExpectedMessages {
schemaTester.ValidateSchemaObject(test.Name, []byte(m))
}
})
}
}
func BenchmarkGetFileKey(b *testing.B) {
db := services.GetDB()
vin := "1F15K3R45N1234567"
id := []byte(`{"file_ids": ["b7d94be8c94062cf","83165a80c940e8b3"]}`)
for n := 0; n < b.N; n++ {
err := handlers.GetFileKeys(db, common.TRex, vin, id)
if err != nil {
b.Error(err)
}
}
}

View File

@@ -0,0 +1,113 @@
package handlers_test
import (
"encoding/json"
"time"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
"github.com/go-pg/pg/v10/orm"
)
var mockRedis *redis.Connection
var mockDB *services.DB
var testDateTime time.Time = time.Date(2022, 1, 2, 3, 4, 5, 6, time.UTC)
func setupRedisMock() {
redis.MockRedisConnection()
mockRedis = &redis.Connection{}
}
func setupDBMock() {
db := services.DB{}
db.SetCarUpdates(&mocks.MockCarUpdates{
SelectCarUpdateResponse: &common.CarUpdate{
ID: 1,
VIN: "FISKER123",
UpdateManifestID: 2,
UpdateManifest: &common.UpdateManifest{
ID: 2,
Name: "TEST_PACKAGE",
Version: "1.0.0",
ReleaseNotes: "http://releasenotes.com",
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
},
},
})
db.SetECU(&mocks.MockEcuDtc{})
db.SetCars(&mocks.MockCars{
SelectResponse: &common.Car{},
})
carVersionLogMock := mocks.MockCarVersionsLog{MockLogVersionChange: func(log *common.CarVersionLogs) (orm.Result, error) {
return nil, nil
}}
db.SetCarVersionsLog(&carVersionLogMock)
mockDB = &db
}
type mockRedisCache struct {
redis.Connection
}
func (c *mockRedisCache) GetSet(id string, data interface{}) error {
driverIDs := []string{"valid-cognito-id-1", "valid-cognito-id-2"}
dataBytes, err := json.Marshal(driverIDs)
if err != nil {
return err
}
err = json.Unmarshal(dataBytes, data)
if err != nil {
return err
}
return nil
}
func NewRedisMock() *tester.MockRedis {
redis.MockRedisConnection()
return &tester.MockRedis{}
}
type AttendentRouteTestCase struct {
Name string
SelectCarUpdate *common.CarUpdate
SelectCarUpdates []common.CarUpdate
CarUpdateError error
SelectCarToDrivers []common.CarToDriver
CarToDriversError error
MockLoadManifest *common.UpdateManifest
MockFileKeysSelect []common.FileKey
MockFileKeysError error
MockEccKeysSelect []common.ECCKeys
tester.RedisTestCase
}
func (at *AttendentRouteTestCase) SetupDB(mockCars *mocks.MockCars, mockCarUpdates *mocks.MockCarUpdates, test *AttendentRouteTestCase) {
if test == nil {
return
}
if mockCars != nil {
mockCars.SelectCarsForDrivers = test.SelectCarToDrivers
mockCars.Error = test.CarToDriversError
}
if mockCarUpdates != nil {
mockCarUpdates.SelectCarUpdateResponse = test.SelectCarUpdate
mockCarUpdates.SelectCarUpdatesResponse = test.SelectCarUpdates
mockCarUpdates.LoadManifest = test.MockLoadManifest
mockCarUpdates.Error = test.CarUpdateError
}
}

View File

@@ -0,0 +1,160 @@
package handlers
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/manifestsender"
"github.com/fiskerinc/cloud-services/pkg/tmobile"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
)
var ENABLE_ORDERUPDATE_SENDS = envtool.GetEnvBool("ENABLE_ORDERUPDATE_SENDS", false)
// Take the feature codes from VehicleOrder message to generate the VOD and CDSs
// Create a new common.UpdateManifest with the VOD and CDS and save it as a common.ConfigUpdateType
// Create a new common.CarUpdate for the VIN and the manifest in step 2 and save it to the database
// Copy the car update id into the UpdateManifest
// Send the UpdateManifest to both trex and hmi
func OrderUpdated(db *services.DB, smsClient sms.SMSServiceClient, vin string, data []byte) error {
logger.Debug().Msgf("Order updated %s", vin)
var order common.VehicleOrder
err := json.Unmarshal(data, &order)
if err != nil {
return err
}
err = setCarSettings(db, vin, order)
if err != nil {
return err
}
err = changeRatePlan(db, smsClient, vin, order.VehicleSpecification.DestinationCountry)
if err != nil {
logger.Error().Msgf("Failed to change rate plan for %s, %s", vin, err)
}
if !ENABLE_ORDERUPDATE_SENDS {
return nil
}
cs := services.GetVehicleConfig()
r := services.RedisClientPool().GetFromPool()
defer r.Close()
trex := manifestsender.NewTBOXManifestSender(r, cs, services.GetDB(), nil, nil)
defer trex.Close()
// I don't think this has ever been called on production, or dev !
input := manifestsender.ProcessConfigUpdateStruct{
VIN: vin,
Name: "SAP order change update",
Username: "unidentified attendant sap user",
SendToCar: true,
DontCreateDatabaseEntry: false,
Forced: false,
}
_, err = trex.ProcessConfigUpdate(input, services.GetDB().GetCarConfigData())
return err
}
func setCarSettings(db *services.DB, vin string, order common.VehicleOrder) (err error) {
// If the sequence number is missing, go will fill it in as 0, which is the case where the car has no sequence number
if order.VehicleSpecification.SequenceNumber != "" {
_, err = db.GetCars().SetSetting(&common.CarSetting{
VIN: vin,
Name: common.SEQUENCE_NUMBER,
Value: fmt.Sprint(order.VehicleSpecification.SequenceNumber),
Type: "string",
})
if err != nil {
return err
}
}
_, err = db.GetCars().SetSetting(&common.CarSetting{
VIN: vin,
Name: common.BODY_COLOR,
Value: common.FeatureCodeToBodyColor(order.VehicleSpecification.VehicleFeatures),
Type: "string",
})
if err != nil {
return err
}
// Incase SAP hasn't sent this value yet
if order.VehicleSpecification.DestinationCountry != "" {
_, err = db.GetCars().SetSetting(&common.CarSetting{
VIN: vin,
Name: common.DELIVERY_DESTINATION,
Value: order.VehicleSpecification.DestinationCountry,
Type: "string",
})
if err != nil {
return err
}
}
return
}
func changeRatePlan(db *services.DB, smsClient sms.SMSServiceClient, vin, destinationCountry string) error {
car, err := services.GetDB().GetCars().SelectByVIN(vin)
if err != nil {
return err
}
if len(car.ICCID) == 0 {
return fmt.Errorf("no iccid found for vehicle %s", vin)
}
iccid := strings.TrimSuffix(strings.ToLower(car.ICCID), "f")
customAtributeReq := sms.CustomAtributesRequest{
ICCID: iccid,
AccountCustom1: destinationCountry,
}
_, err = smsClient.HandleCustomAttributes(context.Background(), &customAtributeReq)
if err != nil {
return err
}
ratePlan, err := db.GetRatePlan().Select(destinationCountry)
if err != nil {
return err
}
changeRatePlanRequest := sms.ChangeRatePlanRequest{
ICCID: iccid,
ProductId: ratePlan.ProductID,
AccountId: tmobile.FISKER_TMOBILE_ACCOUNT_ID,
}
_, err = smsClient.HandleChangeRatePlan(context.Background(), &changeRatePlanRequest)
if err != nil {
return err
}
go verifyRatePlan(smsClient, iccid, destinationCountry)
return nil
}
func verifyRatePlan(smsClient sms.SMSServiceClient, iccid, destinationCountry string) error {
deviceDetailsRequest := sms.DeviceDetailsRequest{
ICCID: iccid,
}
details, err := smsClient.HandleDeviceDetails(context.Background(), &deviceDetailsRequest)
if err != nil {
logger.Error().Msgf("failed to check device details for iccid %s", iccid)
}
logger.Info().Msgf("device details for iccid %s: %+v", iccid, details)
return nil
}

View File

@@ -0,0 +1,224 @@
package handlers_test
import (
"encoding/json"
"fmt"
"testing"
"time"
"github.com/pkg/errors"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
q "github.com/fiskerinc/cloud-services/pkg/db/queries"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
"github.com/stretchr/testify/assert"
)
var (
someErr = errors.New("some error")
vinMock = "FISKER123"
vodMock = "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
confMock = "000000000000000000000000000000000000000"
validOrder = common.VehicleOrder{
SpecID: 800010200,
OrderNumber: 8000102,
MessageIdentifier: "VEHICLEORDERSUBMISSION",
VehicleSpecification: common.VehicleSpecification{
OrderIndicator: "S",
FleetOrderIndicator: "N",
ProductionPhaseIndicator: "01",
VehicleIndicator: "000",
ManufacturingPlant: "G",
ExpectedReferenceDate: common.ExpectedReferenceDate{
Time: time.Date(2022, 5, 26, 0, 0, 0, 0, time.UTC),
},
ModelType: "FM29",
ModelYearIndicator: 2023,
VehicleModel: "F29",
VinPrefix: "VCF1ZBU2_PG",
VehicleFeatures: []common.FeatureCodes{
{
FamilyCode: "2801",
FeatureCode: "280102",
},
{
FamilyCode: "2804",
FeatureCode: "280401",
},
{
FamilyCode: "2805",
FeatureCode: "280501",
},
},
},
}
validUpdateManifest = common.UpdateManifest{
ID: 1,
CarUpdateID: 1,
Version: fmt.Sprint(validOrder.OrderNumber),
Description: fmt.Sprintf("configuration %s %s", vinMock, validOrder.MessageIdentifier),
ManifestType: common.ConfigUpdateType,
VOD: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
}
validRespECUs = []*common.UpdateManifestECU{
{
UpdateManifestID: 1,
ECU: "ICC",
Configuration: confMock,
},
}
smsMock = sms.NewSMSMockSuccess()
mockCars = mocks.MockCars{}
)
func TestOrderUpdated(t *testing.T) {
handlers.ENABLE_ORDERUPDATE_SENDS = true
validUpdateManifestResp := validUpdateManifest
validUpdateManifestResp.ID = 1
validUpdateManifestResp.ECUs = validRespECUs
scrubbedValidUpdateManifest := validUpdateManifestResp.ToUpdateConfigManifest()
scrubbedValidUpdateManifest.Type = "standard"
successPayload, _ := json.Marshal(validOrder)
redisMock := tester.NewRedisMock()
services.SetRedisClientPool(tester.NewMockClientPool(redisMock))
mockSap := vconfig.SAPServiceMock{GetConfigurationMock: func(vin string) (common.SAPResponse, error) {
return common.SAPResponse{
ModelYear: 2023,
ModelType: "Ocean",
VersionDuringModelYear: "1",
Features: []common.SAPFeature{
{
FamilyCode: "FamilyCode1",
FeatureCode: "FeatureCode1",
},
{
FamilyCode: "FamilyCode2",
FeatureCode: "FeatureCode2",
},
{
FamilyCode: "VOD",
FeatureCode: vodMock,
},
}}, nil
}}
services.SetSapService(mockSap)
mockCars.SetLoadResp(&common.Car{
VIN: vinMock,
ICCID: "1234567890",
SoldStatus: common.CarSoldStatusRetailed,
})
tests := map[string]struct {
updateManifest q.UpdateManifestsInterface
cars q.CarsInterface
carsUpdate q.CarUpdatesInterface
ratePlan q.RatePlanInterface
vConfig vconfig.ConfigServiceInterface
payload []byte
expRedisMsgs map[string]interface{}
expErr error
}{
"success": {
updateManifest: &mocks.MockUpdateManifests{SelectResponse: []common.UpdateManifest{{ID: 1234}}},
cars: &mockCars,
carsUpdate: &mocks.MockCarUpdates{},
ratePlan: &mocks.MockRatePlan{},
vConfig: vconfig.ConfigMock{GetVODCDSCodingDataMock: SuccessGetCDSMock},
payload: successPayload,
expRedisMsgs: map[string]interface{}{
"2:" + vinMock: common.Message{
Handler: "car_update",
Data: common.CarUpdate{
ID: 1,
VIN: vinMock,
UpdateManifestID: 1,
UpdateManifest: &validUpdateManifestResp,
},
},
"1:" + vinMock: common.Message{
Handler: "config_update",
Data: scrubbedValidUpdateManifest,
},
},
expErr: nil,
},
"json_parse_err": {
payload: []byte(`12`),
expErr: errors.New("json: cannot unmarshal number into Go value of type common.VehicleOrder"),
},
"failed_cars_db": {
updateManifest: &mocks.MockUpdateManifests{SelectResponse: []common.UpdateManifest{{ID: 1234}}},
cars: &mocks.MockCars{
DBMockHelper: mocks.DBMockHelper{
Error: someErr,
},
},
carsUpdate: &mocks.MockCarUpdates{},
ratePlan: &mocks.MockRatePlan{},
vConfig: vconfig.ConfigMock{GetVODCDSCodingDataMock: SuccessGetCDSMock},
payload: successPayload,
expErr: someErr,
},
"failed_cds": {
updateManifest: &mocks.MockUpdateManifests{SelectResponse: []common.UpdateManifest{{ID: 1234}}},
carsUpdate: &mocks.MockCarUpdates{},
cars: &mockCars,
ratePlan: &mocks.MockRatePlan{},
vConfig: vconfig.ConfigMock{GetVODCDSCodingDataMock: FailedGetCDSMock},
payload: successPayload,
expErr: someErr,
},
"failed_db": {
updateManifest: &mocks.MockUpdateManifests{
DBMockHelper: mocks.DBMockHelper{
Error: someErr,
},
},
cars: &mockCars,
carsUpdate: &mocks.MockCarUpdates{},
ratePlan: &mocks.MockRatePlan{},
vConfig: vconfig.ConfigMock{GetVODCDSCodingDataMock: SuccessGetCDSMock},
payload: successPayload,
expErr: someErr,
},
}
for tname, tt := range tests {
t.Run(tname, func(t *testing.T) {
redisMock.Reset()
db := services.GetDB()
db.SetManifests(tt.updateManifest)
db.SetCars(tt.cars)
db.SetCarUpdates(tt.carsUpdate)
db.SetRatePlan(tt.ratePlan)
services.SetVehicleConfig(tt.vConfig)
err := handlers.OrderUpdated(db, &smsMock, vinMock, tt.payload)
if err != nil && tt.expErr != nil {
assert.Equal(t, tt.expErr.Error(), err.Error())
return
}
assert.Equal(t, tt.expErr, err)
// assert.Equal(t, tt.expRedisMsgs, redisMock.PublishedMessages)
})
}
}
func SuccessGetCDSMock(request common.VODCDSRequest) (map[string]string, error) {
ecus := map[string]string{
"VOD": vodMock,
"ICC": confMock,
}
return ecus, nil
}
func FailedGetCDSMock(request common.VODCDSRequest) (map[string]string, error) {
return nil, someErr
}

View File

@@ -0,0 +1,24 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/attendant/controllers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
)
var PrivateSeed int64 // Used only for testing. Do not change otherwise
func SendManifest(d *services.DB, ka *services.KeepAwake, device common.Device, id string, data []byte) error {
logger.Debug().Msgf("SendManifest %v %s", device, id)
clientPool := services.RedisClientPool()
configService := services.GetVehicleConfig()
generator := controllers.NewManifestSender(clientPool, d, configService, device, ka, PrivateSeed)
defer generator.Release()
return generator.Process(id, data)
}

View File

@@ -0,0 +1,25 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
)
// If the update is not finished installing on the car, we send out an sms to wake the car up.
// If this message is failed to deliver, we need to cancel the car update and set its status as failed
func CarSendManifestSMSWakeUpCallback(d *services.DB, ka *services.KeepAwake, device common.Device, id string, data []byte) (err error) {
logger.Info().Msgf("SMS delivered but not sending TBox manifest %v %s", device, id)
return nil
/*
clientPool := services.RedisClientPool()
sap := services.GetSapService()
configService := services.GetVehicleConfig()
generator := controllers.NewManifestSender(clientPool, d, sap, configService, device, ka, PrivateSeed)
defer generator.Release()
return generator.ContinueTBOXSend(id, data) */
}

View File

@@ -0,0 +1,962 @@
package handlers_test
import (
"fmt"
"strings"
"testing"
"time"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"os"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/common/dbbasemodel"
"github.com/fiskerinc/cloud-services/pkg/common/manifestfingerprintparams"
dbm "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/kafka"
kafkaMock "github.com/fiskerinc/cloud-services/pkg/kafka/mock"
"github.com/fiskerinc/cloud-services/pkg/redis"
rm "github.com/fiskerinc/cloud-services/pkg/redis/tester"
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
"github.com/fiskerinc/cloud-services/pkg/testrunner"
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
"github.com/jinzhu/copier"
)
func TestSendManifest(t *testing.T) {
os.Setenv("APP_SERVICE_NAME", "ATTENDANT")
handlers.PrivateSeed = 123456789
ka := services.NewKeepAwakeService()
ka.SetService(&services.MockKeepAwakeImplementation{})
now := time.Now()
testVIN := "JH4KA7680RC01"
tboxKey := common.TRex.Key(testVIN)
hmiKey := common.HMI.Key(testVIN)
smsKey := "manifest_tbox_send_cache:100"
var bhex common.BinaryHex = []byte("test")
redis.MockRedisConnection()
falsePtrValue := elptr.ElPtr(false)
truePtrValue := elptr.ElPtr(true)
ecuaRollback := []*common.UpdateManifestECU{
{
ID: 100,
UpdateManifestID: 200,
ECU: "ECUA",
Version: "VERSIONOLD",
Mode: "A",
DBModelBase: th.Timestamp,
Files: []*common.UpdateManifestFile{
{
FileID: "FILEIDOLD",
UpdateManifestECUID: 1001,
Filename: "FILENAMEOLD",
URL: "URLOLD",
FileType: common.Software,
WriteRegionID: 700,
WriteRegion: common.MemoryRegion{
Offset: 701,
Length: 702,
},
FileSize: 240,
DBModelBase: th.Timestamp,
},
},
},
}
ecus := []*common.UpdateManifestECU{
{
ECU: "ICC",
Version: "SWVERSION",
HWVersions: []string{"HWVERSION"},
Mode: "D",
SelfDownload: true,
InstallPriority: 13,
Files: []*common.UpdateManifestFile{
{
FileID: "AAAAAAA",
URL: "http://download.com/file1.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
DBModelBase: th.Timestamp,
},
{
FileID: "SHOULD_NOT_BE_IN_UPDATE",
URL: "http://download.com/SHOULD_NOT_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: falsePtrValue,
DBModelBase: th.Timestamp,
},
{
FileID: "MUST_BE_IN_UPDATE",
URL: "http://download.com/MUST_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: truePtrValue,
DBModelBase: th.Timestamp,
},
},
},
{
ECU: "ECUD",
Version: "SWVERSION",
HWVersions: []string{"HWVERSION"},
Mode: "D",
InstallPriority: 4,
Files: []*common.UpdateManifestFile{
{
FileID: "SHOULD_NOT_BE_IN_UPDATE",
URL: "http://download.com/SHOULD_NOT_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: falsePtrValue,
DBModelBase: th.Timestamp,
},
{
FileID: "MUST_BE_IN_UPDATE",
URL: "http://download.com/MUST_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: truePtrValue,
DBModelBase: th.Timestamp,
},
{
FileID: "BBBBBBB",
URL: "http://download.com/file2.bin",
FileSize: 2000,
Checksum: "BBBBBBB",
FileType: common.Calibration,
WriteRegionID: 300,
WriteRegion: common.MemoryRegion{
Offset: 301,
Length: 302,
},
EraseRegionID: 400,
EraseRegion: &common.MemoryRegion{
Offset: 401,
Length: 402,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
{
ID: 100,
UpdateManifestID: 200,
ECU: "PDU",
Version: "PDU-VERS",
HWVersions: []string{"PDU-VERS"},
Mode: "PDU",
ConfigurationMask: "AAAAAAAA",
InstallPriority: 7,
Files: []*common.UpdateManifestFile{
{
FileID: "NOT_BE_IN_UPDATE",
URL: "http://download.com/NOT_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: falsePtrValue,
DBModelBase: th.Timestamp,
},
{
FileID: "MUST_BE_IN_UPDATE",
URL: "http://download.com/MUST_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: truePtrValue,
DBModelBase: th.Timestamp,
},
{
FileID: "AAAAAAAA",
URL: "http://download.com/filea.bin",
FileSize: 2000,
Checksum: "AAAAAAAA",
WriteRegionID: 500,
FileType: common.Calibration,
WriteRegion: common.MemoryRegion{
Offset: 501,
Length: 502,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
{
ID: 100,
UpdateManifestID: 200,
ECU: "ECUA",
Version: "A-VERS",
HWVersions: []string{"A-VERS"},
Mode: "A",
ConfigurationMask: "AAAAAAAA",
InstallPriority: 1,
Files: []*common.UpdateManifestFile{
{
FileID: "FILEID",
UpdateManifestECUID: 100,
Filename: "FILENAME",
URL: "URL",
FileType: common.Calibration,
WriteRegionID: 600,
WriteRegion: common.MemoryRegion{
Offset: 601,
Length: 602,
},
FileSize: 240,
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
}
fingerprint := getTodaysFingerprint()
standardManifest := common.UpdateManifest{
ID: 100,
CarUpdateID: 297,
Name: "TEST",
Version: "MANIFEST_VERSION",
SUMS: "2023.10.01.00",
Description: "TESTDESC",
ReleaseNotes: "http://fiskerinc.com/release_notes",
Type: common.ManifestTypeForced,
Active: truePtrValue,
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
RollbackEnabled: true,
ECUs: ecus,
UpdateDuration: 30,
}
rollbackDisabledManifest := common.UpdateManifest{
ID: 100,
CarUpdateID: 297,
Name: "TEST",
Version: "MANIFEST_VERSION",
SUMS: "2023.10.01.00.E",
Description: "TESTDESC",
ReleaseNotes: "http://fiskerinc.com/release_notes",
Type: "standard",
Active: truePtrValue,
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
ECUs: ecus,
UpdateDuration: 30,
}
standardManifestNoICC := common.UpdateManifest{
ID: 100,
CarUpdateID: 297,
Name: "TEST",
Version: "MANIFEST_VERSION",
SUMS: "2023.10.01.00.E",
Description: "TESTDESC",
ReleaseNotes: "http://fiskerinc.com/release_notes",
Type: "standard",
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
RollbackEnabled: true,
BodyType: "truck",
ECUs: []*common.UpdateManifestECU{
{
ECU: "ECUD",
HWVersions: []string{"HWVERSION"},
Version: "SWVERSION",
Mode: "D",
InstallPriority: 7,
Files: []*common.UpdateManifestFile{
{
FileID: "BBBBBBB",
URL: "http://download.com/file2.bin",
FileSize: 2000,
Checksum: "BBBBBBB",
FileType: common.Calibration,
WriteRegionID: 700,
WriteRegion: common.MemoryRegion{
Offset: 701,
Length: 702,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
{
ID: 100,
UpdateManifestID: 200,
ECU: "PDU",
Version: "PDU-VERS",
HWVersions: []string{"PDU-VERS"},
Mode: "PDU",
ConfigurationMask: "AAAAAAAA",
InstallPriority: 10,
Files: []*common.UpdateManifestFile{
{
FileID: "AAAAAAAA",
URL: "http://download.com/filea.bin",
FileSize: 2000,
FileType: common.Calibration,
Checksum: "AAAAAAAA",
WriteRegionID: 800,
WriteRegion: common.MemoryRegion{
Offset: 801,
Length: 802,
},
EraseRegionID: 900,
EraseRegion: &common.MemoryRegion{
Offset: 901,
Length: 902,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
{
ID: 100,
UpdateManifestID: 200,
ECU: "ECUA",
Version: "A-VERS",
HWVersions: []string{"A-VERS"},
Mode: "A",
ConfigurationMask: "AAAAAAAA",
InstallPriority: 4,
Files: []*common.UpdateManifestFile{
{
FileID: "FILEID",
UpdateManifestECUID: 100,
Filename: "FILENAME",
URL: "URL",
FileType: common.Calibration,
WriteRegionID: 600,
WriteRegion: common.MemoryRegion{
Offset: 601,
Length: 602,
},
FileSize: 240,
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
},
UpdateDuration: 30,
}
mockCarUpdate := &common.CarUpdate{
ID: 297,
VIN: testVIN,
UpdateManifestID: 100,
}
mockCarECUs := []common.CarECU{
{ECU: "ECUD", HWVersion: "HWVERSION"},
{ECU: "ECUA", HWVersion: "A-VERS", Version: "TEST122"},
{ECU: "OBC", HWVersion: "PDU-VERS", Version: "TEST121"},
{ECU: "BCM", HWVersion: ";#A;#"},
{ECU: "ICC", HWVersion: "HWVERSION", Version: "TEST123"},
}
mockRedis := rm.MockRedis{}
mockCUDB := dbm.MockCarUpdates{}
mockManDB := dbm.MockUpdateManifests{}
mockKeys := dbm.MockEccKeys{
MockListResponse: []common.ECCKeys{
{
ECU: "PDU",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
Env: "current",
DBModelBase: dbbasemodel.DBModelBase{
CreatedAt: &now,
UpdatedAt: &now,
},
},
{
ECU: "ECUA",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
Env: "current",
DBModelBase: dbbasemodel.DBModelBase{
CreatedAt: &now,
UpdatedAt: &now,
},
},
{
ECU: "ECUD",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
Env: "current",
DBModelBase: dbbasemodel.DBModelBase{
CreatedAt: &now,
UpdatedAt: &now,
},
},
},
}
services.GetDB().SetCarUpdates(&mockCUDB)
services.GetDB().SetCars(&dbm.MockCars{SelectCarECUs: mockCarECUs, SelectResponse: &common.Car{ICCID: "8000000000000000000"}})
services.GetDB().SetManifests(&mockManDB)
services.GetDB().SetECCKeys(&mockKeys)
services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis))
mockSap := vconfig.SAPServiceMock{GetConfigurationMock: func(vin string) (common.SAPResponse, error) {
return common.SAPResponse{
ModelYear: 2023,
ModelType: "Ocean",
VersionDuringModelYear: "1",
Features: []common.SAPFeature{
{
FamilyCode: "FamilyCode1",
FeatureCode: "FeatureCode1",
},
{
FamilyCode: "FamilyCode2",
FeatureCode: "FeatureCode2",
},
},
}, nil
}}
mockConf := vconfig.ConfigMock{GetVODCDSCodingDataMock: func(request common.VODCDSRequest) (map[string]string, error) {
return map[string]string{
"EPS": "000147303031313632011D",
"ESP": "000F47303031313632000001012301027600000101000002B0",
"ECUA": "config",
"VOD": "",
"ADAS": "000147303031313632011D",
}, nil
}}
mockSMS := &sms.SMSMock{}
mockSMS.SetHandleSMSQueueResponse(&sms.SMSQueueResponse{
SmsMsgID: "100",
SentSuccessful: true,
}, nil)
services.SetSapService(mockSap)
services.SetVehicleConfig(mockConf)
services.SetSmsClient(mockSMS)
mockKafkaProducer := kafkaMock.GetKafkaMock(nil)
services.SetKafkaProducer(mockKafkaProducer)
clonedStandardManifest := common.UpdateManifest{}
clonedStandardManifestNoICC := common.UpdateManifest{}
err := copier.CopyWithOption(&clonedStandardManifest, &standardManifest, copier.Option{DeepCopy: true})
if err != nil {
t.Fatal(err)
}
err = copier.CopyWithOption(&clonedStandardManifestNoICC, &standardManifestNoICC, copier.Option{DeepCopy: true})
if err != nil {
t.Fatal(err)
}
tests := []testrunner.TestCase{
{
Name: "Send manifest w Self Download",
DBTestCase: &dbm.DBTestCase{
MockLoadResponse: mockCarUpdate,
SetupMockResponse: func() {
mockCUDB.LoadManifest = &clonedStandardManifest
mockManDB.ECUUpdatesMock = func(man *common.UpdateManifestECU, vin string) ([]*common.UpdateManifestECU, error) {
if man.ECU == "ECUA" {
return ecuaRollback, nil
}
return nil, nil
}
},
},
RedisTestCase: &rm.RedisTestCase{
Device: common.Service,
DeviceKey: kafka.OTAUpdateService,
PayloadData: `{"car_update_id":297}`,
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"update_manifest","data":{"name":"TEST","version":"MANIFEST_VERSION","description":"TESTDESC","release_notes":"http://fiskerinc.com/release_notes","ecu_updates":[{"name":"ECUA","version":"A-VERS","current_version":"TEST122","hw_version":"A-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"FILEID","url":"URL","file_size":240,"type":"calibration","write_region":{"offset":601,"length":602}}]},{"name":"ECUD","version":"SWVERSION","hw_version":"HWVERSION","files":[{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"BBBBBBB","url":"http://download.com/file2.bin","file_size":2000,"checksum":"BBBBBBB","type":"calibration","write_region":{"offset":301,"length":302},"erase_region":{"offset":401,"length":402}}]},{"name":"OBC","version":"PDU-VERS","current_version":"TEST121","hw_version":"PDU-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"AAAAAAAA","url":"http://download.com/filea.bin","file_size":2000,"checksum":"AAAAAAAA","type":"calibration","write_region":{"offset":501,"length":502}}]},{"name":"ICC","version":"SWVERSION","current_version":"TEST123","hw_version":"HWVERSION","self_download":true,"files":[{"file_id":"AAAAAAA","url":"http://download.com/file1.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}}]}],"fingerprint":"` + fingerprint + `","car_update_id":297,"rollback":true,"type":"forced","vod":"01011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110100202310010011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100001e","update_duration":30}}`,
},
ExpectedCaches: map[string]rm.ExpiringCacheResult{
// carupdateKey: {
// Value: `{"current_size":0,"ecu":"","errorcode":0,"file_size":0,"file_total":0,"id":297,"info":"ICC","installed":0,"status":"sent","total_files":0,"total_size":0}`,
// Expires: expectedExpire,
// },
smsKey: {
Value: `{"VIN":"JH4KA7680RC01","ManifestID":100,"Destination":"ICC"}`,
},
},
},
},
{
Name: "Send manifest w Self Download rollback disabled",
DBTestCase: &dbm.DBTestCase{
MockLoadResponse: mockCarUpdate,
SetupMockResponse: func() {
mockCUDB.LoadManifest = &rollbackDisabledManifest
mockManDB.ECUUpdatesMock = func(man *common.UpdateManifestECU, vin string) ([]*common.UpdateManifestECU, error) {
if man.ECU == "ECUA" {
return ecuaRollback, nil
}
return nil, nil
}
},
},
RedisTestCase: &rm.RedisTestCase{
Device: common.Service,
DeviceKey: kafka.OTAUpdateService,
PayloadData: `{"car_update_id":297}`,
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"update_manifest","data":{"name":"TEST","version":"MANIFEST_VERSION","description":"TESTDESC","release_notes":"http://fiskerinc.com/release_notes","ecu_updates":[{"name":"ECUA","version":"A-VERS","current_version":"TEST122","hw_version":"A-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"FILEID","url":"URL","file_size":240,"type":"calibration","write_region":{"offset":601,"length":602}}]},{"name":"ECUD","version":"SWVERSION","hw_version":"HWVERSION","files":[{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"BBBBBBB","url":"http://download.com/file2.bin","file_size":2000,"checksum":"BBBBBBB","type":"calibration","write_region":{"offset":301,"length":302},"erase_region":{"offset":401,"length":402}}]},{"name":"OBC","version":"PDU-VERS","current_version":"TEST121","hw_version":"PDU-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"AAAAAAAA","url":"http://download.com/filea.bin","file_size":2000,"checksum":"AAAAAAAA","type":"calibration","write_region":{"offset":501,"length":502}}]},{"name":"ICC","version":"SWVERSION","current_version":"TEST123","hw_version":"HWVERSION","self_download":true,"files":[{"file_id":"AAAAAAA","url":"http://download.com/file1.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}}]}],"fingerprint":"` + fingerprint + `","car_update_id":297,"rollback":false,"type":"standard","vod":"0101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111010020231001000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000ba","update_duration":30}}`,
},
ExpectedCaches: map[string]rm.ExpiringCacheResult{
smsKey: {
Value: `{"VIN":"JH4KA7680RC01","ManifestID":100,"Destination":"ICC"}`,
},
},
},
},
{
Name: "Bad message",
RedisTestCase: &rm.RedisTestCase{
Device: common.HMI,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":`,
ExpectedError: "unexpected end of JSON input",
},
},
{
Name: "Send manifest w No Self Download",
DBTestCase: &dbm.DBTestCase{
MockLoadResponse: mockCarUpdate,
SetupMockResponse: func() {
mockCUDB.LoadManifest = &clonedStandardManifestNoICC
},
},
RedisTestCase: &rm.RedisTestCase{
Device: common.Service,
DeviceKey: kafka.OTAUpdateService,
PayloadData: `{"car_update_id":297}`,
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"update_manifest","data":{"name":"TEST","version":"MANIFEST_VERSION","description":"TESTDESC","release_notes":"http://fiskerinc.com/release_notes","ecu_updates":[{"name":"ECUA","version":"A-VERS","current_version":"TEST122","hw_version":"A-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"FILEID","url":"URL","file_size":240,"type":"calibration","write_region":{"offset":601,"length":602}}]},{"name":"ECUD","version":"SWVERSION","hw_version":"HWVERSION","files":[{"file_id":"BBBBBBB","url":"http://download.com/file2.bin","file_size":2000,"checksum":"BBBBBBB","type":"calibration","write_region":{"offset":701,"length":702}}]},{"name":"OBC","version":"PDU-VERS","current_version":"TEST121","hw_version":"PDU-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"AAAAAAAA","url":"http://download.com/filea.bin","file_size":2000,"checksum":"AAAAAAAA","type":"calibration","write_region":{"offset":801,"length":802},"erase_region":{"offset":901,"length":902}}]}],"fingerprint":"` + fingerprint + `","car_update_id":297,"rollback":true,"type":"standard","vod":"0101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111010020231001000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000ba","update_duration":30}}`,
tboxKey: `{"handler":"update_manifest","data":{"ecu_updates":[{"name":"ECUA","version":"A-VERS","current_version":"TEST122","hw_version":"A-VERS","configuration_mask":"AAAAAAAA","configuration":"config","files":[{"file_id":"FILEID","url":"URL","file_size":240,"type":"calibration","write_region":{"offset":601,"length":602}}],"rollback":[{"version":"VERSIONOLD","files":[{"file_id":"FILEIDOLD","url":"URLOLD","file_size":240,"type":"software","write_region":{"offset":701,"length":702}}]}],"ecc_keys":{"level_1":"74657374","level_2":"74657374","level_3":"74657374"}},{"name":"ECUD","version":"SWVERSION","hw_version":"HWVERSION","files":[{"file_id":"BBBBBBB","url":"http://download.com/file2.bin","file_size":2000,"checksum":"BBBBBBB","type":"calibration","write_region":{"offset":701,"length":702}}],"ecc_keys":{"level_1":"74657374","level_2":"74657374","level_3":"74657374"}},{"name":"OBC","version":"PDU-VERS","current_version":"TEST121","hw_version":"PDU-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"AAAAAAAA","url":"http://download.com/filea.bin","file_size":2000,"checksum":"AAAAAAAA","type":"calibration","write_region":{"offset":801,"length":802},"erase_region":{"offset":901,"length":902}}],"ecc_keys":{"level_1":"74657374","level_2":"74657374","level_3":"74657374"}}],"fingerprint":"` + fingerprint + `","car_update_id":297,"rollback":true,"type":"standard","vod":"0101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111010020231001000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000ba","update_duration":30}}`,
},
ExpectedCaches: map[string]rm.ExpiringCacheResult{
"manifest_tbox_send_cache:UQIDEPFQUH": {
Value: `{"VIN":"JH4KA7680RC01","ManifestID":0,"Destination":"ICC/TBOX"}`,
},
},
},
},
}
schemaTesterHMI := th.NewSchemaTestHelper(t, schemaToHMI)
schemaTesterTRex := th.NewSchemaTestHelper(t, schemaToTRex)
for _, test := range tests {
mockRedis.Reset()
for _, ecu := range ecus {
ecu.Rollback = nil
}
if test.DBTestCase != nil {
test.DBTestCase.SetupDB(&mockCUDB)
}
if test.RedisTestCase != nil {
test.RedisTestCase.SetupRedis(&mockRedis)
err := handlers.SendManifest(services.GetDB(), ka, test.RedisTestCase.Device, test.RedisTestCase.DeviceKey, []byte(test.RedisTestCase.PayloadData))
test.RedisTestCase.CheckHandlerError(t, test.Name, err)
test.RedisTestCase.Validate(t, test.Name, &mockRedis)
}
if test.DBTestCase != nil {
test.DBTestCase.Validate(t, test.Name, &mockCUDB)
}
for key, m := range test.RedisTestCase.ExpectedMessages {
name := fmt.Sprintf("%s %s", test.Name, key)
if strings.Contains(key, "2:") {
schemaTesterHMI.ValidateSchemaObject(name, []byte(m))
continue
}
schemaTesterTRex.ValidateSchemaObject(name, []byte(m))
}
}
}
func TestSendManifestMultiFile(t *testing.T) {
os.Setenv("APP_SERVICE_NAME", "ATTENDANT")
testVIN := "JH4KA7680RC01"
now := time.Now()
hmiKey := common.HMI.Key(testVIN)
falsePtrValue := elptr.ElPtr(false)
truePtrValue := elptr.ElPtr(true)
var bhex common.BinaryHex = []byte("test")
redis.MockRedisConnection()
ka := services.NewKeepAwakeService()
ka.SetService(&services.MockKeepAwakeImplementation{})
ecus := []*common.UpdateManifestECU{
{
ECU: "BCM",
Version: "DB22121A",
HWVersions: []string{";#A;#"},
Mode: "D",
SelfDownload: true,
Files: []*common.UpdateManifestFile{
{
FileID: "ShouldNotSee",
Filename: "notparsed.s19",
URL: "fakeurl.com",
Checksum: "shouldnotsee",
Parsed: falsePtrValue,
},
{
FileID: "3ee5fd6bc9402d67",
Filename: "MAGNA_BCM_FBL_driver.s19_0.bin",
URL: "https://upload-dev.fiskerdps.com/4466d78a-50e1-4f1d-a4b8-5f78076758ed/MAGNA_BCM_FBL_driver.s19_0.bin",
FileSize: 1000,
Checksum: "shouldsee",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Bootloader,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
DBModelBase: th.Timestamp,
Parsed: truePtrValue,
},
{
FileID: "7878ede9c9407779",
Filename: "FM29_Application_FR40_220111.s19_0.bin",
URL: "ttps://upload-dev.fiskerdps.com/b5c3c8c8-86cd-422e-aa11-a248697edfa3/FM29_Application_FR40_220111.s19_0.bin",
FileSize: 1000,
Checksum: "SHOULD NOT SEE",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Software,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: truePtrValue,
Signature: "ShouldNOtSee",
DBModelBase: th.Timestamp,
},
{
FileID: "7bb5083dc9407ae9",
Filename: "FM29_Application_FR40_220111.s19_1.bin",
URL: "https://upload-dev.fiskerdps.com/2cdeb566-cb00-42cd-93a8-69769b19269a/FM29_Application_FR40_220111.s19_1.bin",
FileSize: 1000,
Checksum: "shantsee",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Software,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: truePtrValue,
DBModelBase: th.Timestamp,
Signature: "SHOULD NOT SEE",
},
{
FileID: "7ebb4fadc9409b91",
Filename: "FM29_Application_FR40_220111.s19_2.bin",
URL: "https://upload-dev.fiskerdps.com/ec9f8a84-61fb-4a9b-9735-ac6f91b07a83/FM29_Application_FR40_220111.s19_2.bin",
FileType: common.Software,
Parsed: truePtrValue,
Signature: "SomeSignatureYouShouldSee",
},
},
},
}
fingerprint := getTodaysFingerprint()
standardManifest := common.UpdateManifest{
ID: 100,
CarUpdateID: 297,
Name: "TEST",
Version: "MANIFEST_VERSION",
SUMS: "2023.10.01.00",
Description: "TESTDESC",
ReleaseNotes: "http://fiskerinc.com/release_notes",
Type: "standard",
Active: truePtrValue,
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
RollbackEnabled: false,
ECUs: ecus,
UpdateDuration: 30,
}
mockCarUpdate := &common.CarUpdate{
ID: 297,
VIN: testVIN,
UpdateManifestID: 100,
}
mockCarECUs := []common.CarECU{
{ECU: "ECUD", HWVersion: "HWVERSION"},
{ECU: "ECUA", HWVersion: "A-VERS"},
{ECU: "OBC", HWVersion: "PDU-VERS"},
{ECU: "BCM", HWVersion: ";#A;#"},
{ECU: "ICC", HWVersion: "HWVERSION"},
}
mockRedis := rm.MockRedis{}
mockCUDB := dbm.MockCarUpdates{}
mockManDB := dbm.MockUpdateManifests{}
mockKeys := dbm.MockEccKeys{
MockListResponse: []common.ECCKeys{
{
ECU: "BCM",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
Env: "current",
DBModelBase: dbbasemodel.DBModelBase{
CreatedAt: &now,
UpdatedAt: &now,
},
},
},
}
services.GetDB().SetCarUpdates(&mockCUDB)
services.GetDB().SetCars(&dbm.MockCars{SelectCarECUs: mockCarECUs, SelectResponse: &common.Car{ICCID: ""}})
services.GetDB().SetManifests(&mockManDB)
services.GetDB().SetECCKeys(&mockKeys)
services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis))
mockSap := vconfig.SAPServiceMock{GetConfigurationMock: func(vin string) (common.SAPResponse, error) {
return common.SAPResponse{
ModelYear: 2023,
ModelType: "Ocean",
VersionDuringModelYear: "1",
Features: []common.SAPFeature{
{
FamilyCode: "FamilyCode1",
FeatureCode: "FeatureCode1",
},
{
FamilyCode: "FamilyCode2",
FeatureCode: "FeatureCode2",
},
},
}, nil
}}
mockConf := vconfig.ConfigMock{GetVODCDSCodingDataMock: func(request common.VODCDSRequest) (map[string]string, error) {
return map[string]string{
"ECUA": "config",
"VOD": "",
}, nil
}}
mockSMS := &sms.SMSMock{}
mockSMS.SetHandleSMSQueueResponse(&sms.SMSQueueResponse{
SmsMsgID: "100",
SentSuccessful: true,
}, nil)
services.SetSmsClient(mockSMS)
services.SetSapService(mockSap)
services.SetVehicleConfig(mockConf)
smsKey := "manifest_tbox_send_cache:100"
tests := []testrunner.TestCase{
{
Name: "MultiFile right order",
DBTestCase: &dbm.DBTestCase{
MockLoadResponse: mockCarUpdate,
SetupMockResponse: func() {
mockCUDB.LoadManifest = &standardManifest
mockManDB.ECUUpdatesMock = func(man *common.UpdateManifestECU, vin string) ([]*common.UpdateManifestECU, error) {
if man.ECU == "ECUA" {
return nil, nil
}
return nil, nil
}
},
},
RedisTestCase: &rm.RedisTestCase{
Device: common.Service,
DeviceKey: kafka.OTAUpdateService,
PayloadData: `{"car_update_id":297}`,
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"update_manifest","data":{"name":"TEST","version":"MANIFEST_VERSION","description":"TESTDESC","release_notes":"http://fiskerinc.com/release_notes","ecu_updates":[{"name":"BCM","version":"DB22121A","hw_version":";#A;#","self_download":true,"files":[{"file_id":"3ee5fd6bc9402d67","url":"https://upload-dev.fiskerdps.com/4466d78a-50e1-4f1d-a4b8-5f78076758ed/MAGNA_BCM_FBL_driver.s19_0.bin","file_size":1000,"checksum":"shouldsee","type":"bootloader","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"7878ede9c9407779","url":"ttps://upload-dev.fiskerdps.com/b5c3c8c8-86cd-422e-aa11-a248697edfa3/FM29_Application_FR40_220111.s19_0.bin","file_size":1000,"type":"software","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"7bb5083dc9407ae9","url":"https://upload-dev.fiskerdps.com/2cdeb566-cb00-42cd-93a8-69769b19269a/FM29_Application_FR40_220111.s19_1.bin","file_size":1000,"type":"software","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"7ebb4fadc9409b91","url":"https://upload-dev.fiskerdps.com/ec9f8a84-61fb-4a9b-9735-ac6f91b07a83/FM29_Application_FR40_220111.s19_2.bin","type":"software","write_region":{"offset":0,"length":0},"signature":"SomeSignatureYouShouldSee"}]}],"fingerprint":"` + fingerprint + `","car_update_id":297,"rollback":false,"type":"standard","vod":"01011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110100202310010011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100001e","update_duration":30}}`,
},
ExpectedCaches: map[string]rm.ExpiringCacheResult{smsKey: {
Value: `{"VIN":"JH4KA7680RC01","ManifestID":100,"Destination":"ICC"}`,
}},
},
},
}
for _, test := range tests {
mockRedis.Reset()
for _, ecu := range ecus {
ecu.Rollback = nil
}
if test.DBTestCase != nil {
test.DBTestCase.SetupDB(&mockCUDB)
}
if test.RedisTestCase != nil {
test.RedisTestCase.SetupRedis(&mockRedis)
err := handlers.SendManifest(services.GetDB(), ka, test.RedisTestCase.Device, test.RedisTestCase.DeviceKey, []byte(test.RedisTestCase.PayloadData))
test.RedisTestCase.CheckHandlerError(t, test.Name, err)
test.RedisTestCase.Validate(t, test.Name, &mockRedis)
}
if test.DBTestCase != nil {
test.DBTestCase.Validate(t, test.Name, &mockCUDB)
}
}
}
func TestSendManifestIntegration(t *testing.T) {
os.Setenv("APP_SERVICE_NAME", "ATTENDANT")
ka := services.NewKeepAwakeService()
ka.SetService(&services.MockKeepAwakeImplementation{})
err := handlers.SendManifest(services.GetDB(), ka, common.Service, "", []byte(`{"car_update_id":297}`))
if err != nil {
t.Error(err)
}
}
func getTodaysFingerprint() string {
// generate today's fingerprint
fpparams := manifestfingerprintparams.GetFPParams()
var fm = common.UpdateManifest{}
fm.GenerateFingerprint(fpparams.CurTime(), fpparams.ManifestSerial())
return fm.Fingerprint
}

View File

@@ -0,0 +1,55 @@
package handlers
import (
"encoding/json"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/pkg/errors"
)
// UpdateData extracts update ID from byte slice
type UpdateData struct {
ID int `json:"id"`
}
// ApproveUpdate updates DB update field and
//
// sends redis message to vehicle to initialize download
func ApproveUpdate(db *services.DB, id string, data []byte) error {
logger.Debug().Msgf("ApproveUpdate %s", id)
client := services.RedisClientPool().GetFromPool()
defer client.Close()
// TODO: NEEDS VALIDATION THAT INCOMING ID CAN APPROVE
// CAN COME FROM MOBILE OR HMI - NEEDS TO HANDLE BOTH CASES
var u UpdateData
err := json.Unmarshal(data, &u)
if err != nil {
return errors.WithStack(err)
}
update, err := db.ModifyUpdateStatus(u.ID, "approved")
if err != nil {
return err
}
logger.Debug().Msgf("Sending redis queue- %s, key- %s, hander- %s, data- %v", "attendant", update.VIN, "update_download", u)
err = client.SafeQueueMessage(
common.TRex.Key(update.VIN),
common.Message{
Handler: "update_download",
Data: u,
},
)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,28 @@
package handlers_test
import (
"testing"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
)
func TestApproveUpdate(t *testing.T) {
mockRedis := &tester.MockRedis{}
services.SetRedisClientPool(tester.NewMockClientPool(mockRedis))
data := []byte(`{"id": 1}`)
err := handlers.ApproveUpdate(mockDB, "FISKER123", data)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "ApproveUpdate", "no error", err)
}
err = handlers.ApproveUpdate(mockDB, "FISKER123", nil)
if err == nil {
t.Errorf(testhelper.TestErrorTemplate, "ApproveUpdate", "error", err)
}
}

View File

@@ -0,0 +1,70 @@
package handlers
import (
"encoding/json"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/cache"
"github.com/fiskerinc/cloud-services/pkg/common"
s "github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/validator"
"github.com/pkg/errors"
)
// VehicleData extracts VIN from byte slice
type VehicleData struct {
VIN string `json:"vin" validate:"required,vin"`
}
// GetUpdates queries DB for updates based off VIN and
//
// sends redis message back to requester
func GetUpdates(db *services.DB, id string, data []byte) error {
clientPool := services.RedisClientPool()
var v VehicleData
err := json.Unmarshal(data, &v)
if err != nil {
return errors.WithStack(err)
}
err = validator.ValidateStruct(&v)
if err != nil {
return errors.WithStack(err)
}
ok, err := cache.VerifyCarToDriver(clientPool, db.GetCars(), v.VIN, id)
if err != nil {
return err
} else if !ok {
return cache.ErrInvalidCarToDriverAssociation(v.VIN, id)
}
cu := common.CarUpdate{
VIN: v.VIN,
Status: s.InstallApprovalAwait,
}
updates, err := db.GetCarUpdates().Select(&cu, nil)
if err != nil {
return err
}
result := make([]common.ApprovalUpdate, len(updates))
for i := range updates {
result[i] = common.NewApprovalUpdates(&updates[i])
}
client := clientPool.GetFromPool()
defer client.Close()
logger.Debug().Msgf("Sending redis queue- %s, key- %s, hander- %s, data- %v", "attendant", id, "updates", result)
return client.SafeQueueMessage(
common.Mobile.Key(id),
common.Message{
Handler: "updates",
Data: result,
},
)
}

View File

@@ -0,0 +1,148 @@
package handlers_test
import (
"fmt"
"testing"
"time"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
s "github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
"github.com/fiskerinc/cloud-services/pkg/common/dbbasemodel"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
"github.com/pkg/errors"
)
func TestUpdatesGet(t *testing.T) {
mockRedis := &tester.MockRedis{}
services.SetRedisClientPool(tester.NewMockClientPool(mockRedis))
mockCars := &mocks.MockCars{}
mockCarUpdates := &mocks.MockCarUpdates{}
mockDB = &services.DB{}
mockDB.SetCars(mockCars)
mockDB.SetCarUpdates(mockCarUpdates)
cognitoID := "valid-cognito-id-1"
mobileKey := common.Mobile.Key(cognitoID)
vin := "JM1BG2241R0797923"
data := fmt.Sprintf(`{"vin":"%s"}`, vin)
now := time.Now()
selectList := []common.CarUpdate{
{
ID: 1234,
VIN: vin,
UpdateManifestID: 4321,
Status: s.InstallApprovalAwait,
UpdateManifest: &common.UpdateManifest{
Name: "TEST",
Description: "TEST",
ReleaseNotes: "http://releasenotes.com",
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
ECUs: []*common.UpdateManifestECU{
{
ECU: "ADAS",
Version: "VERSION",
Mode: "A",
},
},
DBModelBase: dbbasemodel.DBModelBase{
CreatedAt: &now,
UpdatedAt: &now,
},
},
},
}
carToDrivers := []common.CarToDriver{
{
ID: 2000,
VIN: vin,
DriverID: cognitoID,
},
}
tests := []AttendentRouteTestCase{
{
Name: "No data",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: "",
ExpectedError: "unexpected end of JSON input",
},
},
{
Name: "Bad request",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: "{}",
ExpectedError: "Key: 'VehicleData.VIN' Error:Field validation for 'VIN' failed on the 'required' tag",
},
},
{
Name: "Bad association",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: data,
ExpectedError: "no relationship found between vin JM1BG2241R0797923 and driver valid-cognito-id-1",
},
},
{
Name: "Cache error",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: data,
ExpectedError: "cache error",
},
CarToDriversError: errors.New("cache error"),
},
{
Name: "Good request",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: data,
ExpectedMessages: map[string]string{
mobileKey: `{"handler":"updates","data":[{"id":1234,"vin":"JM1BG2241R0797923","name":"TEST","description":"TEST","release_notes":"http://releasenotes.com"}]}`,
},
},
SelectCarToDrivers: carToDrivers,
SelectCarUpdates: selectList,
},
{
Name: "DB error",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: data,
ExpectedError: "database error",
},
SelectCarToDrivers: carToDrivers,
SelectCarUpdates: nil,
CarUpdateError: errors.New("database error"),
},
}
for i := range tests {
mockRedis.Reset()
test := &tests[i]
test.SetupRedis(mockRedis)
test.SetupDB(mockCars, mockCarUpdates, test)
err := handlers.GetUpdates(mockDB, test.DeviceKey, []byte(test.PayloadData))
test.CheckHandlerError(t, test.Name, err)
test.Validate(t, test.Name, mockRedis)
}
}

View File

@@ -0,0 +1,45 @@
package handlers
import (
"encoding/json"
"time"
"github.com/fiskerinc/cloud-services/services/attendant/controllers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/mongo"
"github.com/fiskerinc/cloud-services/pkg/validator"
"github.com/pkg/errors"
)
func UploadDtc(id string, data []byte) error {
// ID is a vin in this case
logger.Debug().Msgf("Upload DTC for %s", id)
var DTCEntry controllers.DTCEntry
DTCEntry.VIN = id
err := json.Unmarshal(data, &DTCEntry)
if err != nil {
return err
}
if err = validator.GetValidator().Struct(&DTCEntry); err != nil {
return errors.WithMessage(err, "failed structure validation")
}
err = DTCEntry.ParseSnapshot()
if err != nil {
logger.Warn().Msg(err.Error())
return err
}
client, err := services.GetMongoClient()
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
return err
}
dtcs := mongo.NewCollection(client.Collection("dtcs"))
DTCEntry.CreatedAt = time.Now()
_, err = dtcs.InsertOne(DTCEntry)
return err
}