Initial cloud-services repo - gateway service + pkg modules

This commit is contained in:
Chris Rai
2026-01-30 23:14:52 -05:00
commit fbb820d7b3
1037 changed files with 171318 additions and 0 deletions

12
pkg/validator/can_id.go Normal file
View File

@@ -0,0 +1,12 @@
package validator
import (
"regexp"
"github.com/go-playground/validator/v10"
)
func validateCANID(fl validator.FieldLevel) bool {
ok, _ := regexp.Match(`^[0-9]+(-[0-9]+)?$`, []byte(fl.Field().String()))
return ok
}

View File

@@ -0,0 +1,59 @@
package validator_test
import (
"testing"
"fiskerinc.com/modules/testhelper"
"fiskerinc.com/modules/validator"
)
type TestCANID struct {
Name string `validate:"can_id"`
Expected string
}
var canIDValidatorValidTests = []TestCANID{
{Name: "1"},
{Name: "123"},
{Name: "123-456"},
}
func TestValidateCANID(t *testing.T) {
var tests = []TestCANID{
{
Name: "",
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
},
{
Name: "-",
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
},
{
Name: "-123",
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
},
{
Name: "abc",
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
},
{
Name: "ab12",
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
},
{
Name: "123-123-123",
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
},
}
tests = append(tests, canIDValidatorValidTests...)
for _, test := range tests {
err := validator.ValidateStruct(test)
if err == nil && test.Expected != "" {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err)
} else if err != nil && err.Error() != test.Expected {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err.Error())
}
}
}

View File

@@ -0,0 +1,26 @@
package validator
import (
"regexp"
"fiskerinc.com/modules/logger"
"github.com/go-playground/validator/v10"
"github.com/pkg/errors"
)
func validateCertSerial(fl validator.FieldLevel) bool {
ok, err := ValidateCertSerialSimple(fl.Field().String())
if err != nil {
logger.Err(err).Msg("Unable to validate certificate serial number")
}
return ok
}
func ValidateCertSerialSimple(serial string) (bool, error) {
matched, err := regexp.Match(`^([a-zA-Z0-9]{2}[:-]{1}){18,19}[a-zA-Z0-9]{2}$`, []byte(serial))
if err != nil {
return matched, errors.WithStack(err)
}
return matched, nil
}

View File

@@ -0,0 +1,51 @@
package validator_test
import (
"testing"
"fiskerinc.com/modules/testhelper"
"fiskerinc.com/modules/validator"
)
type TestCertSerial struct {
Name string `validate:"serial"`
Expected string
}
var serialValidTests = []TestCertSerial{
{Name: "01:01:7c:d2:d0:6b:ff:30:c5:66:9a:0e:dd:19:0a:61:7d:ca:95:33"},
{Name: "01:03:68:f1:b7:1f:e1:27:90:01:9c:b0:0d:ed:7d:a1:b9:c0:e1:3a"},
{Name: "01:03:68:f1:b7:1f:e1:27:90:01:9c:b0:0d:ed:7d:a1:b9:c0:e1"},
}
func TestValidateSerial(t *testing.T) {
var tests = []TestCertSerial{
{
Name: "",
Expected: "Key: 'TestCertSerial.Name' Error:Field validation for 'Name' failed on the 'serial' tag",
},
{
Name: "testing123",
Expected: "Key: 'TestCertSerial.Name' Error:Field validation for 'Name' failed on the 'serial' tag",
},
{
Name: "01:03:68:f1:b7:1f:e1:27:90:01:9c:b0:0d:ed:7d:a1:b9:c0",
Expected: "Key: 'TestCertSerial.Name' Error:Field validation for 'Name' failed on the 'serial' tag",
},
{
Name: "01:03:68:f1:b7:1f:e1:27:90:01:9c:b0:0d:ed7da1b9c0e13a",
Expected: "Key: 'TestCertSerial.Name' Error:Field validation for 'Name' failed on the 'serial' tag",
},
}
tests = append(tests, serialValidTests...)
for _, test := range tests {
err := validator.ValidateStruct(test)
if err == nil && test.Expected != "" {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err)
} else if err != nil && err.Error() != test.Expected {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err.Error())
}
}
}

31
pkg/validator/dates.go Normal file
View File

@@ -0,0 +1,31 @@
package validator
import (
"regexp"
"github.com/go-playground/validator/v10"
)
const iso8601DateRegexString = "^(?:[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d{1,9})?(?:Z|[+-][01]\\d:[0-5]\\d)$"
const yyyyMMDDDateRegexString = `^\d{4}\-\d{2}\-\d{2}$`
var iso8601DateRegex = regexp.MustCompile(iso8601DateRegexString)
var yyyyMMDDDateRegex = regexp.MustCompile(yyyyMMDDDateRegexString)
func IsISO8601Date(fl validator.FieldLevel) bool {
value := fl.Field().String()
// skip if blank. if required, add required tag
if len(value) == 0 {
return true
}
return iso8601DateRegex.MatchString(value)
}
func IsDateYYYYMMDD(fl validator.FieldLevel) bool {
value := fl.Field().String()
// skip if blank. if required, add required tag
if len(value) == 0 {
return true
}
return yyyyMMDDDateRegex.MatchString(value)
}

12
pkg/validator/ecu.go Normal file
View File

@@ -0,0 +1,12 @@
package validator
import (
"fiskerinc.com/modules/common"
"github.com/go-playground/validator/v10"
)
func validateECU(fl validator.FieldLevel) bool {
ecu := fl.Field().String()
_, ok := common.EcuMap[ecu]
return ok
}

55
pkg/validator/ecu_test.go Normal file
View File

@@ -0,0 +1,55 @@
package validator_test
import (
"testing"
"fiskerinc.com/modules/testhelper"
"fiskerinc.com/modules/validator"
)
type TestECU struct {
Name string `validate:"ecu"`
Expected string
}
var ecuValidTests = []TestECU{
{Name: "BCM"},
{Name: "TBOX"},
{Name: "iBooster"},
}
func TestValidateECU(t *testing.T) {
var tests = []TestECU{
{
Name: "",
Expected: "Key: 'TestECU.Name' Error:Field validation for 'Name' failed on the 'ecu' tag",
},
{
Name: "-",
Expected: "Key: 'TestECU.Name' Error:Field validation for 'Name' failed on the 'ecu' tag",
},
{
Name: "-123",
Expected: "Key: 'TestECU.Name' Error:Field validation for 'Name' failed on the 'ecu' tag",
},
{
Name: "abc",
Expected: "Key: 'TestECU.Name' Error:Field validation for 'Name' failed on the 'ecu' tag",
},
{
Name: "ab12",
Expected: "Key: 'TestECU.Name' Error:Field validation for 'Name' failed on the 'ecu' tag",
},
}
tests = append(tests, ecuValidTests...)
for _, test := range tests {
err := validator.ValidateStruct(test)
if err == nil && test.Expected != "" {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err)
} else if err != nil && err.Error() != test.Expected {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err.Error())
}
}
}

15
pkg/validator/email.go Normal file
View File

@@ -0,0 +1,15 @@
package validator
import (
"regexp"
"github.com/go-playground/validator/v10"
)
func validateEmail(fl validator.FieldLevel) bool {
s := fl.Field().String()
isEmail, _ := regexp.Match(`^\w([\w\-_+]*(\.[\w\-_+]+)?)*@(\w+[\-.])+\w{2,}$`, []byte(s))
hasValidSize, _ := regexp.Match(`^.{0,64}@.{0,63}$`, []byte(s))
return isEmail && hasValidSize
}

View File

@@ -0,0 +1,60 @@
package validator_test
import (
"testing"
"fiskerinc.com/modules/validator"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func Test_validateEmail(t *testing.T) {
tests := map[string]struct {
emailsList []string
expErr error
}{
"valid": {
emailsList: []string{
"simple@example.com",
"very.common@example.com",
"disposable.style.email.with+symbol@example.com",
"other.email-with-hyphen@example.com",
"fully-qualified-domain@example.com",
"user.name+tag+sorting@example.com",
"x@example.com",
"example-indeed@strange-example.com",
"example@s.example",
"user-@example.org",
"2abc.d@mail.com",
},
},
"invalid": {
emailsList: []string{
"1234567890123456789012345678901234567890123456789012345678901234+x@example.com",
"abc.@mail.com",
"abc..def@mail.com",
`"".abc@mail.com`,
"abc#def@mail.com",
"abc.def@mail.c",
"abc.def@mail#archive.com",
"abc.def@mail",
"abc.def@mail..com",
},
expErr: errors.New("Key: '' Error:Field validation for '' failed on the 'email' tag"),
},
}
v := validator.GetValidator()
for tname, tt := range tests {
t.Run(tname, func(t *testing.T) {
for _, email := range tt.emailsList {
err := v.Var(email, "email")
if err != nil && tt.expErr != nil {
assert.Equal(t, tt.expErr.Error(), err.Error())
continue
}
assert.Equal(t, tt.expErr, err)
}
})
}
}

67
pkg/validator/error.go Normal file
View File

@@ -0,0 +1,67 @@
package validator
import (
"fmt"
"strings"
"github.com/go-pg/pg/v10"
"github.com/go-playground/validator/v10"
)
func GetValidationErrorMsg(err error) (bool, string) {
valerr, ok := err.(validator.ValidationErrors)
if ok {
return true, GetError(&valerr)
}
fielderr, ok := err.(*FieldError)
if ok {
return true, fielderr.Error()
}
pgerr, ok := err.(pg.Error)
if ok && pgerr.IntegrityViolation() {
return true, pgerr.Field(68)
}
return false, err.Error()
}
func GetError(errs *validator.ValidationErrors) string {
size := len(*errs)
msg := make([]string, size)
for i, err := range *errs {
key := err.Field()
if len(key) == 0 {
key = err.Tag()
}
msg[i] = fmt.Sprintf("%s %s", key, readableValidationError(err.Tag(), err.Param(), err.Value()))
}
return strings.Join(msg, ". ")
}
func readableValidationError(tag string, param string, value interface{}) string {
switch tag {
case "required":
return tag
case "lte":
return fmt.Sprintf("greater than %s", param)
case "gte":
return fmt.Sprintf("less than %s", param)
case "len":
return fmt.Sprintf("not %s length", param)
case "max":
return fmt.Sprintf("greater than %s length", param)
case "min":
return fmt.Sprintf("less than %s length", param)
case "serial":
return fmt.Sprintf("'%v' invalid", value)
case "vin":
return fmt.Sprintf("'%v' invalid", value)
case "url":
return "invalid url"
default:
return fmt.Sprintf("%s %s", tag, param)
}
}

View File

@@ -0,0 +1,9 @@
package validator
type FieldError struct {
ErrorMsg string
}
func (fe *FieldError) Error() string {
return fe.ErrorMsg
}

View File

@@ -0,0 +1,12 @@
package validator
import (
"regexp"
"github.com/go-playground/validator/v10"
)
func validateFleetName(fl validator.FieldLevel) bool {
ok, _ := regexp.Match(`^[a-zA-Z0-9-]+$`, []byte(fl.Field().String()))
return ok
}

View File

@@ -0,0 +1,51 @@
package validator_test
import (
"testing"
"fiskerinc.com/modules/testhelper"
"fiskerinc.com/modules/validator"
)
type TestFleet struct {
Name string `validate:"fleet"`
Expected string
}
var fleetValidatorValidTests = []TestFleet{
{Name: "t"},
{Name: "test"},
{Name: "Test"},
{Name: "TEST"},
{Name: "test1"},
{Name: "1test"},
{Name: "test-test"},
}
func TestValidateFleetName(t *testing.T) {
var tests = []TestFleet{
{
Name: "",
Expected: "Key: 'TestFleet.Name' Error:Field validation for 'Name' failed on the 'fleet' tag",
},
{
Name: "$test",
Expected: "Key: 'TestFleet.Name' Error:Field validation for 'Name' failed on the 'fleet' tag",
},
{
Name: "test test",
Expected: "Key: 'TestFleet.Name' Error:Field validation for 'Name' failed on the 'fleet' tag",
},
}
tests = append(tests, fleetValidatorValidTests...)
for _, test := range tests {
err := validator.ValidateStruct(test)
if err == nil && test.Expected != "" {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err)
} else if err != nil && err.Error() != test.Expected {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err.Error())
}
}
}

View File

@@ -0,0 +1,27 @@
package validator
import (
"regexp"
"strings"
"github.com/go-playground/validator/v10"
)
// This allows just one order by
func validateSqlOrderBy(fl validator.FieldLevel) bool {
// ensure ORDER BY query section is valid
// only letters and numbers
// this will prevent the sql injection test from sending an error
// because it sets the ORDER BY of a query to
// "CASE WHEN (1=1) THEN vin ELSE year END asc"
strings := strings.Split(fl.Field().String(), " ")
ex := regexp.MustCompile(`^[a-zA-Z0-9_]*$`)
for _, val := range strings {
ok := ex.MatchString(val)
if !ok {
return false
}
}
return true
}

View File

@@ -0,0 +1,51 @@
package validator_test
import (
"testing"
"fiskerinc.com/modules/testhelper"
"fiskerinc.com/modules/validator"
)
type TestPageQueryOptions struct {
Order string `json:"order" validate:"max=512,sqlorder"`
Expected string
}
func TestValidateSqlOrderBy(t *testing.T) {
var tests = []TestPageQueryOptions{
{
Order: "",
Expected: "",
},
{
Order: "COLUMN",
Expected: "",
},
{
Order: "COLUMN DESC",
Expected: "",
},
{
Order: "COL_UMN DESC",
Expected: "",
},
{
Order: "CASE WHEN ('1'='1') THEN vin ELSE year END asc", // sql injection test
Expected: "Key: 'TestPageQueryOptions.Order' Error:Field validation for 'Order' failed on the 'sqlorder' tag",
},
{ // This could be made to be valid
Order: "col1 DESC, col2 DESC",
Expected: "Key: 'TestPageQueryOptions.Order' Error:Field validation for 'Order' failed on the 'sqlorder' tag",
},
}
for _, test := range tests {
err := validator.ValidateStruct(test)
if err == nil && test.Expected != "" {
t.Errorf(testhelper.TestErrorTemplate, test.Order, test.Expected, err)
} else if err != nil && err.Error() != test.Expected {
t.Errorf(testhelper.TestErrorTemplate, test.Order, test.Expected, err.Error())
}
}
}

View File

@@ -0,0 +1,14 @@
package validator
import (
"regexp"
"github.com/go-playground/validator/v10"
)
const regexSUMSVersion = `^\d{4}\.(0[1-9]|1[0-2])\.\d{2}\.\d{2}(\.E{1})?$`
func validateSUMSVersion(fl validator.FieldLevel) bool {
ok, _ := regexp.Match(regexSUMSVersion, []byte(fl.Field().String()))
return ok
}

View File

@@ -0,0 +1,19 @@
package validator
import (
"github.com/go-playground/validator/v10"
)
const (
GEO_LOCATION = "geolocation"
CONNECTED_CAR_FEATURE = "connected_car_feature"
NAV_LOCATION_DATA = "navlocation_data"
ACCEPTED = "ACCEPTED"
DECLINED = "DECLINED"
)
func validateUserConsentName(fl validator.FieldLevel) bool {
s := fl.Field().String()
return s == GEO_LOCATION || s == CONNECTED_CAR_FEATURE || s == NAV_LOCATION_DATA
}

View File

@@ -0,0 +1,91 @@
package validator
import (
"net/url"
"strings"
"sync"
"github.com/go-playground/validator/v10"
)
const TokenRule = "max=2048"
const URLRule = "url,max=32768"
const VersionRule = "max=255"
var instance *validator.Validate
var once sync.Once
// GetValidator creates a singleton validator instance
func GetValidator() *validator.Validate {
once.Do(func() {
instance = validator.New()
instance.RegisterValidation("sqlorder", validateSqlOrderBy)
instance.RegisterValidation("can_id", validateCANID)
instance.RegisterValidation("fleet", validateFleetName)
instance.RegisterValidation("serial", validateCertSerial)
instance.RegisterValidation("vin", validateVIN)
instance.RegisterValidation("vins", validateVINs)
instance.RegisterValidation("vinsuffix", validateVINSuffix)
instance.RegisterValidation("vincheck", validateVinCheckDigit)
instance.RegisterValidation("email", validateEmail)
instance.RegisterValidation("ecu", validateECU)
instance.RegisterValidation("ISO8601date", IsISO8601Date)
instance.RegisterValidation("yyyymmdddate", IsDateYYYYMMDD)
instance.RegisterValidation("sums_version", validateSUMSVersion)
instance.RegisterValidation("user_consent_name", validateUserConsentName)
instance.RegisterValidation("iccid", validateICCID)
})
return instance
}
// ValidateStruct validates declared fields within a struct
// variables must have "validate" tag declared within struct
func ValidateStruct(s interface{}) error {
return GetValidator().Struct(s)
}
func ValidateField(field interface{}, tag string) error {
return GetValidator().Var(field, tag)
}
func ValidateIDField(v int64) error {
if v == 0 {
return &FieldError{
ErrorMsg: "id required",
}
}
return nil
}
func ValidateURL(u string) bool {
_, err := url.ParseRequestURI(u)
return err == nil
}
func ValidateNonRequired(s interface{}) error {
err := GetValidator().Struct(s)
return getNonRequired(err)
}
func getNonRequired(err error) error {
if err == nil {
return nil
}
valerrs, ok := err.(validator.ValidationErrors)
if ok {
nonrequired := make(validator.ValidationErrors, 0)
for _, val := range valerrs {
if !strings.Contains(val.Tag(), "required") {
nonrequired = append(nonrequired, val)
}
}
if len(nonrequired) > 0 {
return nonrequired
}
}
return nil
}

View File

@@ -0,0 +1,201 @@
package validator_test
import (
"fmt"
"testing"
"fiskerinc.com/modules/common"
"fiskerinc.com/modules/testhelper"
"fiskerinc.com/modules/validator"
v "github.com/go-playground/validator/v10"
)
type TestCase struct {
Name string
Validate interface{}
ExpectedError string
}
func TestValidateStruct(t *testing.T) {
tests := []TestCase{
{
Name: "Empty Car",
Validate: common.Car{},
ExpectedError: "VIN required. Year required. Model required. Trim required",
},
{
Name: "Valid Car VIN",
Validate: common.Car{
VIN: "JTKJF5C77E3095776",
},
ExpectedError: "Year required. Model required. Trim required",
},
{
Name: "Valid Car",
Validate: common.Car{
VIN: "JTKJF5C77E3095776",
Year: 2022,
Model: "Ocean",
Trim: "Basic",
},
ExpectedError: "",
},
{
Name: "Invalid Car VIN",
Validate: common.Car{
VIN: "JTKJF5C77E3095776X",
},
ExpectedError: "VIN 'JTKJF5C77E3095776X' invalid. Year required. Model required. Trim required",
},
}
validationTestRunner(t, tests, validator.ValidateStruct)
}
func TestValidateSerialStruct(t *testing.T) {
tests := []TestCase{
{
Name: "Empty Cert",
Validate: common.CertificateRevokeRequest{},
ExpectedError: "Serial required",
},
{
Name: "Valid Cert Serial",
Validate: common.CertificateRevokeRequest{
Serial: "02-89-1f-ec-82-69-8a-ce-59-9c-ab-6a-ad-03-b3-c4-41-bd-0d-26",
},
ExpectedError: "",
},
{
Name: "Valid Cert Serial 2",
Validate: common.CertificateRevokeRequest{
Serial: "02-89-1f-ec-82-69-8a-ce-59-9c-ab-6a-ad-03-b3-c4-41-bd-0d",
},
ExpectedError: "",
},
{
Name: "Wrong Cert Serial",
Validate: common.CertificateRevokeRequest{
Serial: "XXXXXXXXXXX",
},
ExpectedError: "Serial 'XXXXXXXXXXX' invalid",
},
}
validationTestRunner(t, tests, validator.ValidateStruct)
}
func TestValidateNonRequired(t *testing.T) {
tests := []TestCase{
{
Name: "Invalid Car VIN",
Validate: common.Car{
VIN: "JTKJF5C77E3095776X",
Model: "Ocean",
Year: 2021,
},
ExpectedError: "VIN 'JTKJF5C77E3095776X' invalid",
},
{
Name: "Empty Car",
Validate: common.Car{},
ExpectedError: "",
},
{
Name: "Valid Car VIN",
Validate: common.Car{
VIN: "JTKJF5C77E3095776",
},
ExpectedError: "",
},
{
Name: "Valid Car",
Validate: common.Car{
VIN: "JTKJF5C77E3095776",
Year: 2022,
Model: "Ocean",
},
ExpectedError: "",
},
}
validationTestRunner(t, tests, validator.ValidateNonRequired)
}
func validationTestRunner(t *testing.T, tests []TestCase, validatorFunc func(interface{}) error) {
for _, test := range tests {
err := validatorFunc(test.Validate)
if err != nil {
valerrs, ok := err.(v.ValidationErrors)
if ok {
str := validator.GetError(&valerrs)
if str != test.ExpectedError {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.ExpectedError, str)
}
} else {
t.Errorf(testhelper.TestErrorTemplate, test.Name, "ValidationErrors", err)
}
} else if test.ExpectedError != "" {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.ExpectedError, nil)
}
}
}
func TestVINValidation(t *testing.T) {
expected := "VIN '%s' invalid"
test := common.Car{
VIN: "1G1FP87S3GN100062",
Model: "Ocean",
Year: 2021,
Trim: "Basic",
}
err := validator.ValidateStruct(test)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "Good validation", "no errors", err)
}
test.VIN = "1G1FP87S3GN100062XXXXX"
err = validator.ValidateStruct(test)
if err != nil {
_, msg := validator.GetValidationErrorMsg(err)
if msg != fmt.Sprintf(expected, test.VIN) {
t.Errorf(testhelper.TestErrorTemplate, "Bad VIN", fmt.Sprintf(expected, test.VIN), msg)
}
}
test.VIN = "1G1FP87S3GN10006"
err = validator.ValidateStruct(test)
if err != nil {
_, msg := validator.GetValidationErrorMsg(err)
if msg != fmt.Sprintf(expected, test.VIN) {
t.Errorf(testhelper.TestErrorTemplate, "Bad VIN", fmt.Sprintf(expected, test.VIN), msg)
}
}
}
func TestUpdatePackageValidation(t *testing.T) {
up := common.UpdateManifest{}
err := validator.ValidateStruct(up)
if err != nil {
_, msg := validator.GetValidationErrorMsg(err)
expected := "Name required"
if msg != expected {
t.Errorf(testhelper.TestErrorTemplate, "Empty UpdateManifest", expected, msg)
}
}
up.ReleaseNotes = "XXXXX"
err = validator.ValidateStruct(up)
if err != nil {
_, msg := validator.GetValidationErrorMsg(err)
expected := "Name required. ReleaseNotes invalid url"
if msg != expected {
t.Errorf(testhelper.TestErrorTemplate, "Empty UpdateManifest", expected, msg)
}
}
}

72
pkg/validator/vin.go Normal file
View File

@@ -0,0 +1,72 @@
package validator
import (
"regexp"
"strings"
"github.com/go-playground/validator/v10"
"github.com/pkg/errors"
"fiskerinc.com/modules/vindecoder"
)
func validateVIN(fl validator.FieldLevel) bool {
ok := ValidateVINSimple(fl.Field().String())
return ok
}
func validateVINs(fl validator.FieldLevel) bool {
vins := strings.Split(fl.Field().String(), ",")
for _, vin := range vins {
ok := vindecoder.ValidateVINSimple(vin)
if !ok {
return false
}
ok = vindecoder.VerifyVinCheckDigit(vin)
if !ok {
return false
}
}
return true
}
func validateVINSuffix(fl validator.FieldLevel) bool {
ok, err := ValidateVINSuffixSimple(fl.Field().String())
return ok && err == nil
}
func ValidateVINSuffixSimple(vin string) (bool, error) {
matched, err := regexp.Match(`^[a-hj-npr-zA-HJ-NPR-Z0-9]{7}$`, []byte(vin))
if err != nil {
return matched, errors.WithStack(err)
}
return matched, nil
}
func validateICCID(fl validator.FieldLevel) bool {
ok, err := ValidateICCIDSimple(fl.Field().String())
return ok && err == nil
}
func ValidateICCIDSimple(iccid string) (bool, error) {
matched, err := regexp.Match(`^[0-9]{5,50}F{0,1}$`, []byte(iccid))
if err != nil {
return matched, errors.Wrapf(err, "")
}
return matched, nil
}
func validateVinCheckDigit(fl validator.FieldLevel) bool {
var vin = fl.Field().String()
return vindecoder.VerifyVinCheckDigit(vin)
}
func ValidateVINSimple(vin string) (valid bool) {
return vindecoder.ValidateVINSimple(vin)
}

View File

@@ -0,0 +1,53 @@
package validator_test
import (
"fmt"
"testing"
"fiskerinc.com/modules/testhelper"
"fiskerinc.com/modules/validator"
)
func TestValidateVinCheckDigit(t *testing.T) {
// these should pass vin regex validation
//otherwise that error would be received here and the test run would fail because of expected tag name mismatch
tests := []TestCar{
{
VIN: "WVWBN7AN1DE546002",
//Expected: "Key: 'Error:Field' Error:Field validation for 'VIN' failed on the 'vincheck' tag",
Expected: "Key: '' Error:Field validation for '' failed on the 'vincheck' tag",
},
{
VIN: "WVWBC7AN1DE546002",
//Expected: "Key: 'Error:Field' Error:Field validation for 'VIN' failed on the 'vincheck' tag",
Expected: "Key: '' Error:Field validation for '' failed on the 'vincheck' tag",
},
}
tests = append(tests, vinValidatorValidTests...)
for _, test := range tests {
err := validator.ValidateField(test.VIN, "vincheck")
if err == nil && test.Expected != "" {
t.Errorf(testhelper.TestErrorTemplate, test.VIN, test.Expected, err)
} else if err != nil && err.Error() != test.Expected {
t.Errorf(testhelper.TestErrorTemplate, test.VIN, test.Expected, err.Error())
}
fmt.Println(test, err)
}
}
var vwGer2013Valid = "WVWBN7AN6DE546002"
var vwGer2013Invalid1 = "WVWBN7AN1DE546002"
var vwGer2013Invalid2 = "WVWBN7AN6DE54600"
var miniGer2009Valid = "WMWMS335X9TY38985"
var bugatFra1998Valid = "VF9SP3V31JM795073"
var dodgeUsa1998Valid = "1B3ES42C4WD736523"
// TEMP
var fskUsa2021Valid = "1F1BN7AN7MA000001"
// TEMP
//var unkJap2013Valid = "JS1GR7MA7D2101136"
//var audiGer2012Valid = "WAUFFAFM3CA000000"

166
pkg/validator/vin_test.go Normal file
View File

@@ -0,0 +1,166 @@
package validator_test
import (
"testing"
"fiskerinc.com/modules/testhelper"
"fiskerinc.com/modules/validator"
)
type TestCar struct {
VIN string `validate:"vin,len=17"`
Expected string
}
var vinValidatorValidTests = []TestCar{
{
VIN: "1G1FP87S3GN100062",
},
{
VIN: "1HTSDN2NXPH482591",
},
{
VIN: "2C4RDGCG0DR641898",
},
{
VIN: "2FMZA5149YBC02439",
},
{
VIN: "2G1FA1E38E9309317",
},
{
VIN: "2HNYD18245H511789",
},
{
VIN: "3C4PDCBG0ET127145",
},
{
VIN: "4JGBB86E46A022490",
},
{
VIN: "5UXCY6C01M9E72005",
},
{
VIN: "JTDKN3DU7A0198862",
},
{
VIN: "KL1TD56647B195973",
},
{
VIN: "WBABS53402JU44262",
},
{
VIN: "YV1CZ852251176667",
},
}
func TestValidateVIN(t *testing.T) {
var tests = []TestCar{
{
VIN: "XXXXXXXXXXXX",
Expected: "Key: 'TestCar.VIN' Error:Field validation for 'VIN' failed on the 'vin' tag",
},
{
VIN: "XXXXXXXXXXXXXXXXXXXXXX",
Expected: "Key: 'TestCar.VIN' Error:Field validation for 'VIN' failed on the 'vin' tag",
},
}
tests = append(tests, vinValidatorValidTests...)
for _, test := range tests {
err := validator.ValidateStruct(test)
if err == nil && test.Expected != "" {
t.Errorf(testhelper.TestErrorTemplate, test.VIN, test.Expected, err)
} else if err != nil && err.Error() != test.Expected {
t.Errorf(testhelper.TestErrorTemplate, test.VIN, test.Expected, err.Error())
}
}
}
func TestValidateVINs(t *testing.T) {
tests := []struct {
VINs string
Expected string
}{
{
VINs: "1G1FP87S3GN100062,1HTSDN2NXPH482591,2C4RDGCG0DR641898,2FMZA5149YBC02439",
},
{
VINs: "1G1FP87S3GN100062,1HTSDN2NXPH482591,2C4RDGCG0DR641898,2FMZA5149YBC02439,XXXXXXXXXXXX",
Expected: "Key: '' Error:Field validation for '' failed on the 'vins' tag",
},
{
VINs: "XXXXXXXXXXXX,YYYYYYYYYYYY,ZZZZZZZZZZZZ",
Expected: "Key: '' Error:Field validation for '' failed on the 'vins' tag",
},
}
for _, test := range tests {
err := validator.GetValidator().Var(test.VINs, "vins")
if err == nil && test.Expected != "" {
t.Errorf(testhelper.TestErrorTemplate, test.VINs, test.Expected, err)
} else if err != nil && err.Error() != test.Expected {
t.Errorf(testhelper.TestErrorTemplate, test.VINs, test.Expected, err.Error())
}
}
}
func TestValidateVINSuffix(t *testing.T) {
tests := []struct {
vin string
expErr string
}{
{vin: "N100062"},
{vin: "H482591"},
{vin: "R641898"},
{vin: "BC02439"},
{vin: "9309317"},
{vin: "H511789"},
{vin: "T127145"},
{vin: "A022490"},
{vin: "9E72005"},
{vin: "0198862"},
{vin: "B195973"},
{vin: "JU44262"},
{vin: "1176667"},
{vin: "XXXXXX", expErr: "Key: '' Error:Field validation for '' failed on the 'vinsuffix' tag"},
{vin: "XXXXXXXX", expErr: "Key: '' Error:Field validation for '' failed on the 'vinsuffix' tag"},
{vin: "XXXXXXO", expErr: "Key: '' Error:Field validation for '' failed on the 'vinsuffix' tag"},
}
for _, test := range tests {
err := validator.GetValidator().Var(test.vin, "vinsuffix")
if err == nil && test.expErr != "" {
t.Errorf(testhelper.TestErrorTemplate, test.vin, test.expErr, err)
} else if err != nil && err.Error() != test.expErr {
t.Errorf(testhelper.TestErrorTemplate, test.vin, test.expErr, err.Error())
}
}
}
func TestValidateICCIDSimple(t *testing.T) {
tests := []struct {
iccid string
match bool
expErr string
}{
{iccid: "8901882000784174124F", match: true},
{iccid: "8901882000784174124", match: true},
{iccid: "8901882000784174124FF"},
{iccid: "SSSSSSSSS"},
}
for _, test := range tests {
matched, err := validator.ValidateICCIDSimple(test.iccid)
if matched != test.match {
t.Errorf(testhelper.TestErrorTemplate, test.iccid, test.match, matched)
}
if err == nil && test.expErr != "" {
t.Errorf(testhelper.TestErrorTemplate, test.iccid, test.expErr, err)
} else if err != nil && err.Error() != test.expErr {
t.Errorf(testhelper.TestErrorTemplate, test.iccid, test.expErr, err.Error())
}
}
}