Skip to content

Commit

Permalink
As 3155 handle approximate location in telemetry (#101)
Browse files Browse the repository at this point in the history
* Add queries for approximate lat and long

* Update e2e test to use gql client

* Allow all time location to query approximate

* Add permission test for the approximate location

* Add unit test for oneOf

* Add signal de-duplication

* Add approximate e2e test

* Update shared version

* Update dockerfile with h3 C dependencies
  • Loading branch information
KevinJoiner authored Nov 26, 2024
1 parent f3120fa commit 9bbb4e5
Show file tree
Hide file tree
Showing 28 changed files with 2,022 additions and 847 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ COPY . ./
RUN make tidy
RUN make build

FROM gcr.io/distroless/static AS final
FROM gcr.io/distroless/base AS final

LABEL maintainer="DIMO <hello@dimo.zone>"

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ help:
@echo ""

build: ## Build the binary
@CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(ARCH) \
@CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(ARCH) \
go build -o $(PATHINSTBIN)/$(BIN_NAME) ./cmd/$(BIN_NAME)

run: build ## Run the binary
Expand Down
200 changes: 200 additions & 0 deletions e2e/aproximate_location_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package e2e_test

import (
"testing"
"time"

"github.com/DIMO-Network/model-garage/pkg/vss"
"github.com/DIMO-Network/telemetry-api/internal/repositories"
"github.com/DIMO-Network/telemetry-api/internal/service/ch"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/uber/h3-go/v4"
)

func TestApproximateLocation(t *testing.T) {
services := GetTestServices(t)
locationTime := time.Date(2024, 11, 20, 22, 28, 17, 0, time.UTC)
// Set up test data in Clickhouse
startLoc := h3.LatLng{Lat: 40.75005062700222, Lng: -74.0094415688571}
endLoc := h3.LatLng{Lat: 40.73899538333504, Lng: -73.99386110247163}
signals := []vss.Signal{
{
Source: ch.SourceTranslations["smartcar"],
Timestamp: locationTime.Add(-time.Hour * 24),
Name: vss.FieldCurrentLocationLatitude,
ValueNumber: startLoc.Lat,
TokenID: 39718,
},
{
Source: ch.SourceTranslations["smartcar"],
Timestamp: locationTime.Add(-time.Hour * 24),
Name: vss.FieldCurrentLocationLongitude,
ValueNumber: startLoc.Lng,
TokenID: 39718,
},
{
Source: ch.SourceTranslations["smartcar"],
Timestamp: locationTime,
Name: vss.FieldCurrentLocationLatitude,
ValueNumber: endLoc.Lat,
TokenID: 39718,
},
{
Source: ch.SourceTranslations["smartcar"],
Timestamp: locationTime,
Name: vss.FieldCurrentLocationLongitude,
ValueNumber: endLoc.Lng,
TokenID: 39718,
},
}

insertSignal(t, services.CH, signals)

// Create and set up GraphQL server
telemetryClient := NewGraphQLServer(t, services.Settings)

// Execute the query
query := `
query {
signalsLatest(tokenId: 39718) {
lastSeen
currentLocationApproximateLatitude{
timestamp
value
}
currentLocationApproximateLongitude{
timestamp
value
}
}
}`

// Create auth token for vehicle
token := services.Auth.CreateVehicleToken(t, "39718", []int{8})

// Execute request
result := ApproxResult{}
err := telemetryClient.Post(query, &result, WithToken(token))
require.NoError(t, err)

expectedStartLatLong := repositories.GetApproximateLoc(startLoc.Lat, startLoc.Lng)
expectedEndLatLong := repositories.GetApproximateLoc(endLoc.Lat, endLoc.Lng)
// Assert the results
assert.Equal(t, locationTime.Format(time.RFC3339), result.SignalLatest.LastSeen)
assert.Equal(t, locationTime.Format(time.RFC3339), result.SignalLatest.ApproxLat.Timestamp)
assert.Equal(t, locationTime.Format(time.RFC3339), result.SignalLatest.ApproxLong.Timestamp)
assert.Equal(t, expectedEndLatLong.Lat, result.SignalLatest.ApproxLat.Value)
assert.Equal(t, expectedEndLatLong.Lng, result.SignalLatest.ApproxLong.Value)

// verify we do not leak the exact location
query = `query {
signalsLatest(tokenId: 39718) {
lastSeen
speed {
timestamp
value
}
currentLocationApproximateLatitude{
timestamp
value
}
currentLocationApproximateLongitude{
timestamp
value
}
currentLocationLatitude{
timestamp
value
}
currentLocationLongitude{
timestamp
value
}
}
}`

// Execute request
result = ApproxResult{}
err = telemetryClient.Post(query, &result, WithToken(token))
require.Error(t, err)

// Assert the results
assert.Equal(t, locationTime.Format(time.RFC3339), result.SignalLatest.LastSeen)
assert.Equal(t, locationTime.Format(time.RFC3339), result.SignalLatest.ApproxLat.Timestamp)
assert.Equal(t, locationTime.Format(time.RFC3339), result.SignalLatest.ApproxLong.Timestamp)
assert.Equal(t, expectedEndLatLong.Lat, result.SignalLatest.ApproxLat.Value)
assert.Equal(t, expectedEndLatLong.Lng, result.SignalLatest.ApproxLong.Value)
assert.Nil(t, result.SignalLatest.Lat)
assert.Nil(t, result.SignalLatest.Long)

query = `query {
signals(tokenId:39718, from: "2020-04-15T09:21:19Z", to: "2025-04-27T09:21:19Z", interval:"24h"){
timestamp
currentLocationApproximateLatitude(agg: FIRST)
currentLocationApproximateLongitude(agg: FIRST)
}
}`
// Execute request
aggResult := ApproxAgg{}
err = telemetryClient.Post(query, &aggResult, WithToken(token))
require.NoError(t, err)

require.Len(t, aggResult.Signals, 2)
// Assert the results
assert.Equal(t, locationTime.Add(-time.Hour*24).Truncate(time.Hour*24).Format(time.RFC3339), aggResult.Signals[0].Timestamp)
assert.Equal(t, expectedStartLatLong.Lat, *aggResult.Signals[0].ApproxLat)
assert.Equal(t, expectedStartLatLong.Lng, *aggResult.Signals[0].ApproxLong)

assert.Equal(t, locationTime.Truncate(time.Hour*24).Format(time.RFC3339), aggResult.Signals[1].Timestamp)
assert.Equal(t, expectedEndLatLong.Lat, *aggResult.Signals[1].ApproxLat)
assert.Equal(t, expectedEndLatLong.Lng, *aggResult.Signals[1].ApproxLong)

query = `query {
signals(tokenId:39718, from: "2020-04-15T09:21:19Z", to: "2025-04-27T09:21:19Z", interval:"24h"){
timestamp
currentLocationApproximateLatitude(agg: FIRST)
currentLocationApproximateLongitude(agg: FIRST)
currentLocationLatitude(agg: FIRST)
currentLocationLongitude(agg: FIRST)
}
}`
// Execute request
aggResult = ApproxAgg{}
err = telemetryClient.Post(query, &aggResult, WithToken(token))
require.Error(t, err)

// Assert the results
require.Len(t, aggResult.Signals, 2)
assert.Equal(t, locationTime.Add(-time.Hour*24).Truncate(time.Hour*24).Format(time.RFC3339), aggResult.Signals[0].Timestamp)
assert.Equal(t, expectedStartLatLong.Lat, *aggResult.Signals[0].ApproxLat)
assert.Equal(t, expectedStartLatLong.Lng, *aggResult.Signals[0].ApproxLong)
assert.Nil(t, aggResult.Signals[0].Lat)
assert.Nil(t, aggResult.Signals[0].Long)

assert.Equal(t, locationTime.Truncate(time.Hour*24).Format(time.RFC3339), aggResult.Signals[1].Timestamp)
assert.Equal(t, expectedEndLatLong.Lat, *aggResult.Signals[1].ApproxLat)
assert.Equal(t, expectedEndLatLong.Lng, *aggResult.Signals[1].ApproxLong)
assert.Nil(t, aggResult.Signals[1].Lat)
assert.Nil(t, aggResult.Signals[1].Long)
}

type ApproxResult struct {
SignalLatest struct {
LastSeen string `json:"lastSeen"`
ApproxLat *SignalWithTime `json:"currentLocationApproximateLatitude"`
ApproxLong *SignalWithTime `json:"currentLocationApproximateLongitude"`
Lat *SignalWithTime `json:"currentLocationLatitude"`
Long *SignalWithTime `json:"currentLocationLongitude"`
} `json:"signalsLatest"`
}

type ApproxAgg struct {
Signals []struct {
Timestamp string `json:"timestamp"`
ApproxLat *float64 `json:"currentLocationApproximateLatitude"`
ApproxLong *float64 `json:"currentLocationApproximateLongitude"`
Lat *float64 `json:"currentLocationLatitude"`
Long *float64 `json:"currentLocationLongitude"`
} `json:"signals"`
}
141 changes: 141 additions & 0 deletions e2e/permission_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package e2e_test

import (
"strconv"
"testing"

"github.com/stretchr/testify/require"
)

func TestPermission(t *testing.T) {
services := GetTestServices(t)
const unusedTokenID = 999999
// Create and set up GraphQL server
telemetryClient := NewGraphQLServer(t, services.Settings)

tests := []struct {
name string
tokenID int
query string
permissions []int
expectedErr string // checking error strings since that is what the server returns
}{
{
name: "No permissions",
tokenID: unusedTokenID,
query: `query {
signalsLatest(tokenId: 39718) {
lastSeen
}
}`,
permissions: []int{},
expectedErr: `[{"message":"unauthorized: token id does not match","path":["signalsLatest"]}]`,
},
{
name: "Token permissions",
tokenID: 39718,
query: `query {
signalsLatest(tokenId: 39718) {
lastSeen
}
}`,
permissions: []int{},
},
{
name: "Partial permissions",
tokenID: 39718,
query: `query {
signalsLatest(tokenId: 39718) {
lastSeen
speed {
value
}
}
}`,
permissions: []int{},
expectedErr: `[{"message":"unauthorized: missing required privilege(s) VEHICLE_NON_LOCATION_DATA","path":["signalsLatest","speed"]}]`,
},
{
name: "Non Location permissions",
tokenID: 39718,
query: `query {
signalsLatest(tokenId: 39718) {
lastSeen
speed {
value
}
}
}`,
permissions: []int{1},
},
{
name: "Location permissions",
tokenID: 39718,
query: `query {
signalsLatest(tokenId: 39718) {
lastSeen
currentLocationLatitude {
value
}
}
}`,
permissions: []int{4},
},
{
name: "Approximate Location permissions",
tokenID: 39718,
query: `query {
signalsLatest(tokenId: 39718) {
lastSeen
currentLocationApproximateLatitude {
value
}
}
}`,
permissions: []int{8},
},
{
name: "Approximate Location with ALL_TIME permission",
tokenID: 39718,
query: `query {
signalsLatest(tokenId: 39718) {
lastSeen
currentLocationApproximateLatitude {
value
}
}
}`,
permissions: []int{4},
},

{
name: "Neither Location nor Approximate Location permissions",
tokenID: 39718,
query: `query {
signalsLatest(tokenId: 39718) {
lastSeen
currentLocationApproximateLatitude {
value
}
}
}`,
permissions: []int{1},
expectedErr: `[{"message":"unauthorized: requires at least one of the following privileges [VEHICLE_APPROXIMATE_LOCATION VEHICLE_ALL_TIME_LOCATION]" ,"path":["signalsLatest","currentLocationApproximateLatitude"]}]`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
token := services.Auth.CreateVehicleToken(t, strconv.Itoa(tt.tokenID), tt.permissions)
// Execute request
result := map[string]any{}
err := telemetryClient.Post(tt.query, &result, WithToken(token))
if tt.expectedErr != "" {
require.Error(t, err)
require.JSONEq(t, tt.expectedErr, err.Error())
return
}
require.NoError(t, err)
})
}
}
10 changes: 7 additions & 3 deletions e2e/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import (
"context"
"encoding/json"
"fmt"
"net/http/httptest"
"os"
"sync"
"testing"

"github.com/99designs/gqlgen/client"
"github.com/DIMO-Network/clickhouse-infra/pkg/container"
"github.com/DIMO-Network/model-garage/pkg/cloudevent"
"github.com/DIMO-Network/nameindexer"
Expand Down Expand Up @@ -114,7 +114,7 @@ func StoreSampleVC(ctx context.Context, idxSrv *indexrepo.Service, bucket string
return nil
}

func NewGraphQLServer(t *testing.T, settings config.Settings) *httptest.Server {
func NewGraphQLServer(t *testing.T, settings config.Settings) *client.Client {
t.Helper()

logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
Expand All @@ -126,5 +126,9 @@ func NewGraphQLServer(t *testing.T, settings config.Settings) *httptest.Server {

t.Cleanup(application.Cleanup)

return httptest.NewServer(application.Handler)
return client.New(application.Handler)
}

func WithToken(token string) client.Option {
return client.AddHeader("Authorization", "Bearer "+token)
}
Loading

0 comments on commit 9bbb4e5

Please sign in to comment.