package handlers_test import ( "context" "encoding/base64" "fmt" "net/http" "testing" "otaupdate/handlers" "otaupdate/services" "github.com/fiskerinc/cloud-services/pkg/common" dbm "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks" "github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc" htc "github.com/fiskerinc/cloud-services/pkg/httpclient/tester" "github.com/fiskerinc/cloud-services/pkg/httphandlers" "github.com/fiskerinc/cloud-services/pkg/kafka" km "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" uhelpers "github.com/fiskerinc/cloud-services/pkg/usecase_helpers" "github.com/fiskerinc/cloud-services/pkg/utils/whereami" "github.com/fiskerinc/cloud-services/pkg/validator" "github.com/go-pg/pg/v10" "google.golang.org/protobuf/proto" ) func TestCarUpdateAdd(t *testing.T) { whereami.SetService(whereami.OTA) redis.MockRedisConnection() mockDB := dbm.MockCarUpdates{} mockKafka := km.KafkaMock{} mockRedis := rm.MockRedis{} vin := "1G1FP87S3GN100062" mockFoa := FoaServiceMock{} services.SetFoaService(&mockFoa) services.GetDB().SetCarUpdates(&mockDB) services.SetKafkaProducer(&mockKafka) services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis)) otaUpdateKey := common.Service.Key(vin) attendentTopic := kafka.AttendantServiceGRPCKafka updateMsg := &kafka_grpc.GRPC_AttendantPayload_UpdateManifest{ UpdateManifest: &kafka_grpc.UpdateManifest{ CarUpdateId: 1, }, } kafkaMSG := kafka_grpc.GRPC_AttendantPayload{ Handler: "send_manifest", Data: updateMsg, } binaryPayload, _ := proto.Marshal(&kafkaMSG) validMessage := fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(binaryPayload)) standardManifest := common.UpdateManifest{ ID: 100, Name: "TEST", Version: "10000", Type: "standard", Country: "US", PowerTrain: "MD23", Restraint: "None", Model: "Ocean", Trim: "Sport", Year: 2022, BodyType: "truck", ECUs: []*common.UpdateManifestECU{ { ECU: "ICC", Version: "SWVERSION", HWVersions: []string{"HWVERSION"}, Mode: "ICC", SelfDownload: true, Files: []*common.UpdateManifestFile{ { FileID: "AAAAAAA", URL: "http://download.com/file1.bin", FileSize: 1000, Checksum: "AAAAAAA", WriteRegionID: 100, WriteRegion: common.MemoryRegion{ Offset: 101, Length: 102, }, EraseRegionID: 200, EraseRegion: &common.MemoryRegion{ Offset: 201, Length: 202, }, }, }, }, { ECU: "ECU", Version: "SWVERSION", HWVersions: []string{"HWVERSION"}, Mode: "D", Files: []*common.UpdateManifestFile{ { FileID: "BBBBBBB", URL: "http://download.com/file2.bin", FileSize: 2000, Checksum: "BBBBBBB", WriteRegionID: 300, WriteRegion: common.MemoryRegion{ Offset: 301, Length: 302, }, EraseRegionID: 400, EraseRegion: &common.MemoryRegion{ Offset: 401, Length: 402, }, }, }, }, }, } forcedManifest := standardManifest forcedManifest.Type = "forced" tests := []testrunner.TestCase{ { Name: "Bad car ids", HttpTestCase: &htc.HttpTestCase{ Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{ UpdateManifestID: 1, }), ExpectedStatus: http.StatusBadRequest, ExpectedResponse: `{"message":"VINs required","error":"Bad Request"}`, }, }, { Name: "No data", HttpTestCase: &htc.HttpTestCase{ Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", nil), ExpectedStatus: http.StatusBadRequest, ExpectedResponse: `{"message":"UpdateManifestID required. VINs required","error":"Bad Request"}`, }, }, { Name: "Bad package ids", HttpTestCase: &htc.HttpTestCase{ Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{ VINs: []string{"NONEXISTENT", "NONEXISTENT2"}, }), ExpectedStatus: http.StatusBadRequest, ExpectedResponse: `{"message":"UpdateManifestID required. VINs[0] 'NONEXISTENT' invalid. VINs[1] 'NONEXISTENT2' invalid","error":"Bad Request"}`, }, }, { Name: "Good data standard manifest id", HttpTestCase: &htc.HttpTestCase{ Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{ UpdateManifestID: 1, VINs: []string{"1G1FP87S3GN100062"}, }), ExpectedStatus: http.StatusOK, ExpectedResponse: `{"data":[{"id":1,"vin":"1G1FP87S3GN100062","manifest_id":1,"status":"pending","username":"testusername","UpdateSource":"OTA"}]}`, }, DBTestCase: &dbm.DBTestCase{ SetupMockResponse: func() { services.GetDB().SetUpdateManifests(&dbm.MockUpdateManifests{ LoadResponse: &standardManifest, }) }, }, RedisTestCase: &rm.RedisTestCase{}, KafkaTestCase: &km.KafkaTestCase{ ExpectedProduceMessages: map[string]map[string]interface{}{ attendentTopic: { otaUpdateKey: validMessage, }, }, }, }, { Name: "Case where manifest id does not exist", HttpTestCase: &htc.HttpTestCase{ Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{ UpdateManifestID: 1, VINs: []string{"1G1FP87S3GN100062"}, }), ExpectedStatus: http.StatusNotFound, ExpectedResponse: `{"message":"pg: no rows in result set","error":"Not Found"}`, Setup: func() { services.GetDB().SetUpdateManifests(&dbm.MockUpdateManifests{ LoadResponse: nil, DBMockHelper: dbm.DBMockHelper{ Error: pg.ErrNoRows, }, }) }, }, }, { Name: "Good data forced manifest id", HttpTestCase: &htc.HttpTestCase{ Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{ UpdateManifestID: 1, VINs: []string{"1G1FP87S3GN100062"}, }), ExpectedStatus: http.StatusOK, ExpectedResponse: `{"data":[{"id":1,"vin":"1G1FP87S3GN100062","manifest_id":1,"status":"pending","username":"testusername","UpdateSource":"OTA"}]}`, }, DBTestCase: &dbm.DBTestCase{ SetupMockResponse: func() { services.GetDB().SetUpdateManifests(&dbm.MockUpdateManifests{ LoadResponse: &forcedManifest, }) }, }, RedisTestCase: &rm.RedisTestCase{}, KafkaTestCase: &km.KafkaTestCase{ ExpectedProduceMessages: map[string]map[string]interface{}{ attendentTopic: { otaUpdateKey: validMessage, }, }, }, }, { Name: "Error", HttpTestCase: &htc.HttpTestCase{ Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{ UpdateManifestID: 1, VINs: []string{"1G1FP87S3GN100062"}, }), ExpectedStatus: http.StatusServiceUnavailable, ExpectedResponse: `{"message":"something went wrong","error":"Service Unavailable"}`, }, DBTestCase: &dbm.DBTestCase{ MockError: fmt.Errorf("something went wrong"), }, }, } for _, test := range tests { mockRedis.Reset() mockKafka.Reset() if test.DBTestCase != nil { test.DBTestCase.SetupDB(&mockDB) } if test.RedisTestCase != nil { test.RedisTestCase.SetupRedis(&mockRedis) } if test.KafkaTestCase != nil { test.KafkaTestCase.Setup(&mockKafka) } if test.HttpTestCase != nil { if test.HttpTestCase.Setup != nil { test.HttpTestCase.Setup() } ctx := context.WithValue(test.HttpTestCase.Request.Context(), httphandlers.ClientIDContextKey, "testusername") test.HttpTestCase.Request = test.HttpTestCase.Request.WithContext(ctx) w := test.HttpTestCase.Test(handlers.HandleCarUpdatesAdd) test.HttpTestCase.ValidateHttp(t, test.Name, w) } if test.DBTestCase != nil { test.DBTestCase.Validate(t, test.Name, &mockDB) } if test.RedisTestCase != nil { test.RedisTestCase.Validate(t, test.Name, &mockRedis) } if test.KafkaTestCase != nil { test.KafkaTestCase.Validate(t, test.Name, &mockKafka) } } } func TestJSONCarUpdatesRequestValidation(t *testing.T) { request := uhelpers.JSONCarUpdatesRequest{ UpdateManifestID: 1, VINs: []string{}, } err := validator.ValidateStruct(request) if err == nil { t.Errorf(th.TestErrorTemplate, "Validate VINs count", nil, err) } else { _, msg := validator.GetValidationErrorMsg(err) expected := "VINs less than 1" if msg != expected { t.Errorf(th.TestErrorTemplate, "Validate VINs count", expected, msg) } } request.VINs = []string{"1G1FP87S3GN100062"} err = validator.ValidateStruct(request) if err != nil { t.Errorf(th.TestErrorTemplate, "Validate Good VIN", nil, err) } request.VINs = []string{"1G1FP87S3GN10006I"} err = validator.ValidateStruct(request) if err == nil { t.Errorf(th.TestErrorTemplate, "Validate Bad VIN", nil, err) } else { _, msg := validator.GetValidationErrorMsg(err) expected := "VINs[0] '1G1FP87S3GN10006I' invalid" if msg != expected { t.Errorf(th.TestErrorTemplate, "Validate Bad VIN", expected, msg) } } } type FoaServiceMock struct{} func (f *FoaServiceMock) OtaUpdateStatus(vin string, carUpdate *common.CarUpdate, status *common.CarUpdateProgress) (*http.Response, error) { return &http.Response{StatusCode: 200}, nil }