Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

As 3062 update attestationi api to work with ruptela payloads #26

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions charts/attestation-api/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ env:
TOKEN_EXCHANGE_JWK_KEY_SET_URL: http://dex-roles-rights-prod.prod.svc.cluster.local:5556/keys
TOKEN_EXCHANGE_ISSUER_URL: https://auth-roles-rights.dimo.zone
VEHICLE_NFT_ADDRESS: '0xbA5738a18d83D41847dfFbDC6101d37C69c9B0cF'
AFTERMARKET_NFT_ADDRESS: '0x9c94C395cBcBDe662235E0A9d3bB87Ad708561BA'
SYNTHETIC_NFT_ADDRESS: '0x4804e8D1661cd1a1e5dDdE1ff458A7f878c0aC6D'
DEFINITIONS_GRPC_ADDR: device-definitions-api-prod:8086
TELEMETRY_URL: http://telemetry-api.dimo.zone
IDENTITY_API_URL: https://identity-api.dimo.zone
Expand All @@ -21,6 +23,7 @@ env:
STATUS_BUCKET_NAME: dimo-network-status-prod
VINVC_BUCKET: dimo-network-vinvc-prod
POMVC_BUCKET: dimo-network-pomvc-prod
CLOUDEVENT_BUCKET: dimo-ingest-cloudevent-prod
DIMO_REGISTRY_CHAIN_ID: 137
ingress:
enabled: true
Expand Down
3 changes: 3 additions & 0 deletions charts/attestation-api/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ env:
TOKEN_EXCHANGE_JWK_KEY_SET_URL: http://dex-roles-rights.dev.svc.cluster.local:5556/keys
TOKEN_EXCHANGE_ISSUER_URL: https://auth-roles-rights.dev.dimo.zone
VEHICLE_NFT_ADDRESS: '0x45fbCD3ef7361d156e8b16F5538AE36DEdf61Da8'
AFTERMARKET_NFT_ADDRESS: '0x325b45949C833986bC98e98a49F3CA5C5c4643B5'
SYNTHETIC_NFT_ADDRESS: '0x78513c8CB4D6B6079f813850376bc9c7fc8aE67f'
TELEMETRY_URL: http://telemetry-api.dev.dimo.zone
IDENTITY_API_URL: https://identity-api.dev.dimo.zone
FINGERPRINT_BUCKET: dimo-network-fingerprint-dev
Expand All @@ -46,6 +48,7 @@ env:
VINVC_DATA_TYPE: VINVCv0.0
POMVC_BUCKET: dimo-network-pomvc-dev
POMVC_DATA_TYPE: POMVCv0.1
CLOUDEVENT_BUCKET: dimo-ingest-cloudevent-dev
S3_AWS_REGION: us-east-2
DIMO_REGISTRY_CHAIN_ID: 80002
service:
Expand Down
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ require (
github.com/ClickHouse/clickhouse-go/v2 v2.30.0
github.com/DIMO-Network/clickhouse-infra v0.0.3
github.com/DIMO-Network/device-definitions-api v1.2.48
github.com/DIMO-Network/model-garage v0.3.1
github.com/DIMO-Network/nameindexer v0.0.7
github.com/DIMO-Network/model-garage v0.3.4-0.20241105133604-ee59bebdd374
github.com/DIMO-Network/nameindexer v0.0.8
github.com/DIMO-Network/shared v0.12.1
github.com/aws/aws-sdk-go-v2 v1.32.2
github.com/aws/aws-sdk-go-v2/credentials v1.17.41
Expand All @@ -23,6 +23,7 @@ require (
github.com/piprate/json-gold v0.5.0
github.com/prometheus/client_golang v1.20.5
github.com/rs/zerolog v1.33.0
github.com/segmentio/ksuid v1.0.4
github.com/stretchr/testify v1.9.0
github.com/swaggo/swag v1.16.4
github.com/uber/h3-go/v4 v4.1.2
Expand Down Expand Up @@ -83,9 +84,10 @@ require (
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/tidwall/gjson v1.17.3 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.55.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
Expand Down
17 changes: 11 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ github.com/DIMO-Network/clickhouse-infra v0.0.3 h1:B6/4IY9IxLcyydET14IjHUT+A5SDE
github.com/DIMO-Network/clickhouse-infra v0.0.3/go.mod h1:NtpQ1btkPzebDvpYYygeqiiBmJ/q5oJb/T/JWzUVRlk=
github.com/DIMO-Network/device-definitions-api v1.2.48 h1:dWFBT4Htdy66PZzI5maq2fxJveDk3+cD04nE2+3lylk=
github.com/DIMO-Network/device-definitions-api v1.2.48/go.mod h1:c9oOnRXxPQvgQOBKYkaf3ZIcpb9fQkdmd/kHmNk6ZR8=
github.com/DIMO-Network/model-garage v0.3.1 h1:1DBRKAL7drOicSuYzj5qKMytYKJ5GJcsKJQggmdybyI=
github.com/DIMO-Network/model-garage v0.3.1/go.mod h1:7lZc6XMwz699tSJwkBev3GZhVsOBtOWOBLTndrSj36o=
github.com/DIMO-Network/nameindexer v0.0.7 h1:aigO9WZGvoGkyu2R92ZXZGT44zB3O0bgLB+A7wbmEsk=
github.com/DIMO-Network/nameindexer v0.0.7/go.mod h1:3X4sme/KVDNZ4A0YNGt/ssfVgSkSRxJ1RKmQYE/Vtqo=
github.com/DIMO-Network/model-garage v0.3.4-0.20241105133604-ee59bebdd374 h1:XJcwnFhivnHUUOGfQXNgUMGZemNm1kLZy/yCVd48siE=
github.com/DIMO-Network/model-garage v0.3.4-0.20241105133604-ee59bebdd374/go.mod h1:w8TaTi9XJ33R4wh/AYfbKUJBBVYy5E+Ayc4CmDfPcYA=
github.com/DIMO-Network/nameindexer v0.0.8 h1:UuBMcdeVaHn0FzNYiT0ysv89LWDLWNn8gt4h7QZBH7c=
github.com/DIMO-Network/nameindexer v0.0.8/go.mod h1:+X3K7NN7/DTwkwex5G0e+cqyUaGoCqqk8otOyjDxN+s=
github.com/DIMO-Network/shared v0.12.1 h1:vYnXZSZKR3tzixXreTAuDx9OFe37m7Xzoacx9C5+UAM=
github.com/DIMO-Network/shared v0.12.1/go.mod h1:UbxVeHgaEeTo7QrzB0t1oVDh+5y79lIPqr8gv65e6Pk=
github.com/DIMO-Network/yaml v0.1.0 h1:KQ3oKHUZETchR6Pxbmmol3e4ewrPv/q8cEwqxfwyZbU=
Expand Down Expand Up @@ -632,6 +632,8 @@ github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5A
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
Expand Down Expand Up @@ -687,14 +689,17 @@ github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5
github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8=
github.com/testcontainers/testcontainers-go/modules/clickhouse v0.33.0 h1:YbB5DBkpgY+GlGPFqTSV1hzWPm3ZHirEyooZrj+ZXK0=
github.com/testcontainers/testcontainers-go/modules/clickhouse v0.33.0/go.mod h1:qJuMPl9yWIWasmdBILM2uDk1Ny1kdeigcKMJ6A8PZz0=
github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
Expand Down
11 changes: 7 additions & 4 deletions internal/app/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,23 +66,26 @@ func createHttpController(logger *zerolog.Logger, settings *config.Settings, sta
vinValidateSerivce := vinvalidator.New(deviceDefGRPCClient)

// Initialize fingerprint repository
fingerprintRepo := fingerprint.New(chConn, s3Client, settings.FingerprintBucket, settings.FingerprintDataType)
fingerprintRepo := fingerprint.New(chConn, s3Client, settings.CloudEventBucket, settings.FingerprintBucket, settings.FingerprintDataType)

// Initialize VC repository
vcRepo := vcrepo.New(chConn, s3Client, settings.VINVCBucket, settings.VINVCDataType, settings.POMVCBucket, settings.POMVCDataType)

// Initialize identity API client
identityAPI, err := identity.NewService(settings.IdentityAPIURL, nil)
identityAPI, err := identity.NewService(settings.IdentityAPIURL, settings.AfterMarketNFTAddress, settings.SyntheticNFTAddress, nil)
if err != nil {
return nil, fmt.Errorf("failed to create identity service: %w", err)
}

// Initialize VC service using the initialized services
vinvcService := vinvc.NewService(logger, vcRepo, identityAPI, fingerprintRepo, vinValidateSerivce, issuer, revokedList, settings.VehicleNFTAddress)

conRepo := connectivity.NewConnectivityRepo(chConn, s3Client, settings.AutoPiDataType, settings.AutoPiBucketName, settings.HashDogDataType, settings.HashDogBucketName, settings.StatusDataType, settings.StatusBucketName)
conRepo := connectivity.NewConnectivityRepo(chConn, s3Client, settings.AutoPiDataType, settings.AutoPiBucketName, settings.HashDogDataType, settings.HashDogBucketName, settings.StatusDataType, settings.StatusBucketName, settings.CloudEventBucket)

pomService := pom.NewService(logger, identityAPI, conRepo, vcRepo, issuer, settings.VehicleNFTAddress)
pomService, err := pom.NewService(logger, identityAPI, conRepo, vcRepo, issuer, settings.VehicleNFTAddress)
if err != nil {
return nil, fmt.Errorf("failed to create POM service: %w", err)
}

ctrl, err := httphandlers.NewVCController(vinvcService, pomService, settings.TelemetryURL)
if err != nil {
Expand Down
46 changes: 35 additions & 11 deletions internal/attestation/apis/identity/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@ import (
"time"

"github.com/DIMO-Network/attestation-api/internal/models"
"github.com/DIMO-Network/model-garage/pkg/cloudevent"
"github.com/ethereum/go-ethereum/common"
)

// Service interacts with the identity GraphQL API.
type Service struct {
httpClient *http.Client
apiQueryURL string
httpClient *http.Client
apiQueryURL string
aftermarketAddr common.Address
SyntheticAddr common.Address
}

// NewService creates a new instance of Service with optional TLS certificate pool.
func NewService(apiBaseURL string, certPool *x509.CertPool) (*Service, error) {
func NewService(apiBaseURL string, aftermarketAddr, SyntheticAddr string, certPool *x509.CertPool) (*Service, error) {
// Configure HTTP client with optional TLS certificate pool.
httpClient := &http.Client{
Timeout: 10 * time.Second,
Expand All @@ -39,19 +42,26 @@ func NewService(apiBaseURL string, certPool *x509.CertPool) (*Service, error) {
if err != nil {
return nil, fmt.Errorf("create idenitiy URL: %w", err)
}

if !common.IsHexAddress(aftermarketAddr) {
return nil, fmt.Errorf("invalid aftermarket address: %s", aftermarketAddr)
}
if !common.IsHexAddress(SyntheticAddr) {
return nil, fmt.Errorf("invalid Synthetic address: %s", SyntheticAddr)
}
return &Service{
apiQueryURL: path,
httpClient: httpClient,
apiQueryURL: path,
httpClient: httpClient,
aftermarketAddr: common.HexToAddress(aftermarketAddr),
SyntheticAddr: common.HexToAddress(SyntheticAddr),
}, nil
}

// GetVehicleInfo fetches vehicle information from the identity API.
func (s *Service) GetVehicleInfo(ctx context.Context, tokenID uint32) (*models.VehicleInfo, error) {
func (s *Service) GetVehicleInfo(ctx context.Context, vehicleDID cloudevent.NFTDID) (*models.VehicleInfo, error) {
requestBody := map[string]any{
"query": query,
"variables": map[string]any{
"tokenId": tokenID,
"tokenId": vehicleDID.TokenID,
},
}

Expand Down Expand Up @@ -92,24 +102,38 @@ func (s *Service) GetVehicleInfo(ctx context.Context, tokenID uint32) (*models.V

var pairedDevices []models.PairedDevice
if respBody.Data.Vehicle.AftermarketDevice != nil {
tokenId := respBody.Data.Vehicle.AftermarketDevice.TokenID
did := cloudevent.NFTDID{
ChainID: vehicleDID.ChainID,
TokenID: uint32(tokenId),
ContractAddress: s.aftermarketAddr,
}
pairedDevices = append(pairedDevices, models.PairedDevice{
Address: common.HexToAddress(respBody.Data.Vehicle.AftermarketDevice.Address),
DID: did,
Address: respBody.Data.Vehicle.AftermarketDevice.Address,
Type: models.DeviceTypeAftermarket,
ManufacturerName: respBody.Data.Vehicle.AftermarketDevice.Manufacturer.Name,
IMEI: respBody.Data.Vehicle.AftermarketDevice.IMEI,
})
}
if respBody.Data.Vehicle.SyntheticDevice != nil {
tokenID := respBody.Data.Vehicle.SyntheticDevice.TokenID
did := cloudevent.NFTDID{
ChainID: vehicleDID.ChainID,
TokenID: uint32(tokenID),
ContractAddress: s.SyntheticAddr,
}
pairedDevices = append(pairedDevices, models.PairedDevice{
Address: common.HexToAddress(respBody.Data.Vehicle.SyntheticDevice.Address),
DID: did,
Address: respBody.Data.Vehicle.SyntheticDevice.Address,
Type: models.DeviceTypeSynthetic,
})
}
if respBody.Data.Vehicle.Definition == nil || respBody.Data.Vehicle.Definition.ID.value == nil {
return nil, fmt.Errorf("vehicle is missing definition ID")
}
vehicleInfo := &models.VehicleInfo{
TokenID: tokenID,
DID: vehicleDID,
PairedDevices: pairedDevices,
NameSlug: *respBody.Data.Vehicle.Definition.ID.value,
}
Expand Down
55 changes: 34 additions & 21 deletions internal/attestation/apis/identity/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/DIMO-Network/attestation-api/internal/attestation/apis/identity"
"github.com/DIMO-Network/attestation-api/internal/models"
"github.com/DIMO-Network/model-garage/pkg/cloudevent"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
Expand All @@ -19,8 +20,19 @@ import (
const testSlug = "toyota_tacoma-4wd_2023"

func TestService_GetPairedDevices(t *testing.T) {
deviceAddr1 := randAddress()
deviceAddr2 := randAddress()
vehicleAddr := randAddress()
aftermarketAddr := randAddress()
syntheticAddr := randAddress()
deviceDID1 := cloudevent.NFTDID{
ChainID: 137,
TokenID: 123,
ContractAddress: aftermarketAddr,
}
deviceDID2 := cloudevent.NFTDID{
ChainID: 137,
TokenID: 789,
ContractAddress: syntheticAddr,
}
ctx := context.Background()
tests := []struct {
name string
Expand All @@ -41,21 +53,21 @@ func TestService_GetPairedDevices(t *testing.T) {
"id": "toyota_tacoma-4wd_2023"
},
"aftermarketDevice": {
"Address": "%s"
"tokenId": %d
},
"syntheticDevice": {
"Address": "%s"
"tokenId": %d
}
}
}
}`, deviceAddr1, deviceAddr2),
}`, deviceDID1.TokenID, deviceDID2.TokenID),
mockStatusCode: http.StatusOK,
expectedInfo: &models.VehicleInfo{
TokenID: 123,
DID: cloudevent.NFTDID{TokenID: 123, ChainID: 137, ContractAddress: vehicleAddr},
NameSlug: testSlug,
PairedDevices: []models.PairedDevice{
{Address: deviceAddr1, Type: models.DeviceTypeAftermarket},
{Address: deviceAddr2, Type: models.DeviceTypeSynthetic},
{DID: deviceDID1, Type: models.DeviceTypeAftermarket},
{DID: deviceDID2, Type: models.DeviceTypeSynthetic},
},
},
expectedError: false,
Expand All @@ -75,7 +87,7 @@ func TestService_GetPairedDevices(t *testing.T) {
}`,
mockStatusCode: http.StatusOK,
expectedInfo: &models.VehicleInfo{
TokenID: 125,
DID: cloudevent.NFTDID{TokenID: 125, ChainID: 137, ContractAddress: vehicleAddr},
NameSlug: testSlug,
},
expectedError: false,
Expand Down Expand Up @@ -112,17 +124,17 @@ func TestService_GetPairedDevices(t *testing.T) {
"id": "toyota_tacoma-4wd_2023"
},
"aftermarketDevice": {
"Address": "%s"
"tokenId": %d
}
}
}
}`, deviceAddr1),
}`, deviceDID1.TokenID),
mockStatusCode: http.StatusOK,
expectedInfo: &models.VehicleInfo{
TokenID: 128,
DID: cloudevent.NFTDID{TokenID: 128, ChainID: 137, ContractAddress: vehicleAddr},
NameSlug: testSlug,
PairedDevices: []models.PairedDevice{
{Address: deviceAddr1, Type: models.DeviceTypeAftermarket},
{DID: deviceDID1, Type: models.DeviceTypeAftermarket},
},
},
expectedError: false,
Expand All @@ -138,17 +150,17 @@ func TestService_GetPairedDevices(t *testing.T) {
"id": "toyota_tacoma-4wd_2023"
},
"syntheticDevice": {
"Address": "%s"
"tokenId": %d
}
}
}
}`, deviceAddr2),
}`, deviceDID2.TokenID),
mockStatusCode: http.StatusOK,
expectedInfo: &models.VehicleInfo{
TokenID: 129,
DID: cloudevent.NFTDID{TokenID: 129, ChainID: 137, ContractAddress: vehicleAddr},
NameSlug: testSlug,
PairedDevices: []models.PairedDevice{
{Address: deviceAddr2, Type: models.DeviceTypeSynthetic},
{DID: deviceDID2, Type: models.DeviceTypeSynthetic},
},
},
expectedError: false,
Expand All @@ -164,11 +176,11 @@ func TestService_GetPairedDevices(t *testing.T) {
"id": null
},
"syntheticDevice": {
"Address": "%s"
"tokenId": %d
}
}
}
}`, deviceAddr2),
}`, deviceDID2.TokenID),
mockStatusCode: http.StatusOK,
expectedInfo: nil,
expectedError: true,
Expand All @@ -194,11 +206,12 @@ func TestService_GetPairedDevices(t *testing.T) {
certPool := x509.NewCertPool()
certPool.AddCert(server.Certificate())

service, err := identity.NewService(server.URL, certPool)
service, err := identity.NewService(server.URL, aftermarketAddr.Hex(), syntheticAddr.Hex(), certPool)
require.NoError(t, err)

// Run the test.
devices, err := service.GetVehicleInfo(ctx, tt.vehicleTokenID)
vehicleDID := cloudevent.NFTDID{TokenID: tt.vehicleTokenID, ChainID: 137, ContractAddress: vehicleAddr}
devices, err := service.GetVehicleInfo(ctx, vehicleDID)
if tt.expectedError {
require.Error(t, err)
require.Nil(t, devices)
Expand Down
Loading