add openhack files

This commit is contained in:
Ryan Peters
2022-11-03 16:41:13 -04:00
commit b2c9f7e29f
920 changed files with 118861 additions and 0 deletions

View File

@ -0,0 +1,140 @@
package tripsgo
import (
"database/sql"
"encoding/json"
"flag"
"fmt"
"os"
"github.com/joho/godotenv"
)
//load .env locally for integration tests
//System Environment variables take precedence
var dbEnv = godotenv.Load()
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
var (
debug = flag.Bool("debug", false, "enable debugging")
password = flag.String("password", getEnv("SQL_PASSWORD", "changeme"), "the database password")
port = flag.Int("port", 1433, "the database port")
server = flag.String("server", getEnv("SQL_SERVER", "changeme.database.windows.net"), "the database server")
user = flag.String("user", getEnv("SQL_USER", "YourUserName"), "the database user")
database = flag.String("d", getEnv("SQL_DBNAME", "mydrivingDB"), "db_name")
driver = flag.String("driver", getEnv("SQL_DRIVER", "mssql"), "db driver")
)
func RebindDataAccessEnvironmentVariables() {
s := getEnv("SQL_SERVER", "changeme.database.windows.net")
server = &s
dr := getEnv("SQL_DRIVER", "mssql")
driver = &dr
}
// ExecuteNonQuery - Execute a SQL query that has no records returned (Ex. Delete)
func ExecuteNonQuery(query string) (string, error) {
connString := fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;port=%d", *server, *database, *user, *password, *port)
if *debug {
message := fmt.Sprintf("connString:%s\n", connString)
logMessage(message)
}
conn, err := sql.Open(*driver, connString)
if err != nil {
return "", err
}
defer conn.Close()
statement, err := conn.Prepare(query)
if err != nil {
return "", err
}
defer statement.Close()
result, err := statement.Exec()
if err != nil {
return "", err
}
serializedResult, _ := json.Marshal(result)
return string(serializedResult), nil
}
// ExecuteQuery - Executes a query and returns the result set
func ExecuteQuery(query string) (*sql.Rows, error) {
connString := fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;port=%d", *server, *database, *user, *password, *port)
conn, err := sql.Open(*driver, connString)
if err != nil {
logError(err, "Failed to connect to database.")
return nil, err
}
defer conn.Close()
statement, err := conn.Prepare(query)
if err != nil {
return nil, err
// log.Fatal("Failed to query a trip: ", err.Error())
}
defer statement.Close()
rows, err := statement.Query()
if err != nil {
return nil, err
// log.Fatal("Error while running the query: ", err.Error())
}
return rows, nil
}
// FirstOrDefault - returns the first row of the result set.
func FirstOrDefault(query string) (*sql.Row, error) {
connString := fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;port=%d", *server, *database, *user, *password, *port)
if *debug {
message := fmt.Sprintf("connString:%s\n", connString)
logMessage(message)
}
conn, err := sql.Open(*driver, connString)
if err != nil {
return nil, err
// log.Fatal("Failed to connect to the database: ", err.Error())
}
defer conn.Close()
statement, err := conn.Prepare(query)
if err != nil {
return nil, err
// log.Fatal("Failed to query a trip: ", err.Error())
}
defer statement.Close()
row := statement.QueryRow()
return row, nil
}

View File

@ -0,0 +1,170 @@
package tripsgo
import (
"bytes"
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestExecuteQueryInvalidDriverReturnsErr(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
InitLogging(os.Stdout, os.Stdout, os.Stdout)
os.Setenv("SQL_DRIVER", "not_a_real_driver")
RebindDataAccessEnvironmentVariables()
//act
var query = SelectAllTripsForUserQuery("someUser")
_, err := ExecuteQuery(query)
//assert
assert.NotNil(t, err)
}
func TestExecuteQueryConnectionSuccess(t *testing.T) {
//act
var query = SelectAllTripsForUserQuery("someUser")
trips, err := ExecuteQuery(query)
//assert
assert.NotNil(t, trips)
assert.Nil(t, err)
}
func TestExecuteQueryInvalidSqlReturnsErr(t *testing.T) {
//arrange
InitLogging(os.Stdout, os.Stdout, os.Stdout)
//act
var invalidSql = "Select Trips From *"
_, err := ExecuteQuery(invalidSql)
//assert
assert.NotNil(t, err)
}
func TestExecuteQueryInvalidServerReturnsErr(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
InitLogging(os.Stdout, os.Stdout, os.Stdout)
os.Setenv("SQL_SERVER", "not_a_real_driver")
RebindDataAccessEnvironmentVariables()
//act
_, err := ExecuteQuery("SELECT TOP 1 ID FROM Trips")
//assert
assert.NotNil(t, err)
}
func TestExecuteNonQueryInvalidDriverReturnsErr(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
InitLogging(os.Stdout, os.Stdout, os.Stdout)
os.Setenv("SQL_DRIVER", "not_a_real_driver")
RebindDataAccessEnvironmentVariables()
//act
_, err := ExecuteNonQuery("fake non query sql")
//assert
assert.NotNil(t, err)
}
func TestExecuteNonQueryConnectionSuccess(t *testing.T) {
//act
_, err := ExecuteNonQuery("SELECT TOP 1 ID FROM Trips")
//assert
assert.Nil(t, err)
}
func TestExecuteNonQueryInvalidServerReturnsErr(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
InitLogging(os.Stdout, os.Stdout, os.Stdout)
os.Setenv("SQL_SERVER", "not_a_real_server")
RebindDataAccessEnvironmentVariables()
//act
_, err := ExecuteNonQuery("SELECT TOP 1 ID FROM Trips")
//assert
assert.NotNil(t, err)
}
func TestFirstOrDefaultInvalidDriverReturnsErr(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
InitLogging(os.Stdout, os.Stdout, os.Stdout)
os.Setenv("SQL_DRIVER", "not_a_real_driver")
RebindDataAccessEnvironmentVariables()
//act
_, err := FirstOrDefault("fake non query sql")
//assert
assert.NotNil(t, err)
}
func TestFirstOrDefaultConnectionSuccess(t *testing.T) {
//act
RebindDataAccessEnvironmentVariables()
_, err := FirstOrDefault("SELECT TOP 1 ID FROM Trips")
//assert
assert.Nil(t, err)
}
func TestFirstOrDefaultInvalidServerReturnsErr(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
InitLogging(os.Stdout, os.Stdout, os.Stdout)
os.Setenv("SQL_SERVER", "not_a_real_server")
RebindDataAccessEnvironmentVariables()
//act
_, err := FirstOrDefault("SELECT TOP 1 ID FROM Trips")
//assert
assert.NotNil(t, err)
}
func TestExecuteNonQueryWritesLogIfDebugTrue(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
info := new(bytes.Buffer)
InitLogging(info, os.Stdout, os.Stdout)
var tr bool = true
debug = &tr
//act
ExecuteNonQuery("SELECT TOP 1 ID FROM Trips")
//assert
actual := fmt.Sprint(info)
assert.True(t, actual != "")
}
func TestFirstOrDefaultWritesLogIfDebugTrue(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
info := new(bytes.Buffer)
InitLogging(info, os.Stdout, os.Stdout)
var tr bool = true
debug = &tr
//act
FirstOrDefault("SELECT TOP 1 ID FROM Trips")
//assert
actual := fmt.Sprint(info)
assert.True(t, actual != "")
}

View File

@ -0,0 +1,24 @@
package tripsgo
import (
"encoding/json"
"strings"
)
// SerializeError - Serialize Error information to JSON format.
func SerializeError(e error, customMessage string) string {
var errorMessage struct {
Message string
}
if customMessage != "" {
message := []string{customMessage, e.Error()}
errorMessage.Message = strings.Join(message, ": ")
} else {
errorMessage.Message = e.Error()
}
serializedError, _ := json.Marshal(errorMessage)
return string(serializedError)
}

View File

@ -0,0 +1,28 @@
package tripsgo
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSerializeErrorReturnsJsonIncludesErrorMessageUnit(t *testing.T) {
//arrange
expected := "{\"Message\":\"This is a fake error\"}"
err := errors.New("This is a fake error")
//act
actual := SerializeError(err, "")
//assert
assert.Equal(t, expected, actual)
}
func TestSerializeErrorReturnsJsonIncludesCustomMessageUnit(t *testing.T) {
//arrange
expected := "{\"Message\":\"more data: This is a fake error\"}"
err := errors.New("This is a fake error")
//act
actual := SerializeError(err, "more data")
//assert
assert.Equal(t, expected, actual)
}

View File

@ -0,0 +1,11 @@
package tripsgo
// ErrorResponseDefault - Structure to return error information to service caller.
type ErrorResponseDefault struct {
// Error code (if available)
Status int32 `json:"status,omitempty"`
// Error Message
Message string `json:"message,omitempty"`
}

View File

@ -0,0 +1,15 @@
package tripsgo
import (
"encoding/json"
"net/http"
)
func healthcheckGet(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
hc := &Healthcheck{Message: "Trip Service Healthcheck", Status: "Healthy"}
json.NewEncoder(w).Encode(hc)
}

View File

@ -0,0 +1,31 @@
package tripsgo
import (
"io/ioutil"
"os"
"testing"
)
var healthRouteTests = []APITestCase{
{
Tag: "t0 - healthcheck",
Method: "GET",
URL: "/api/healthcheck/trips",
Status: 200,
ExpectedResponse: `{"message": "Trip Service Healthcheck","status": "Healthy"}`,
},
}
func TestHealthRouteUnit(t *testing.T) {
router := NewRouter()
var debug, present = os.LookupEnv("DEBUG_LOGGING")
if present && debug == "true" {
InitLogging(os.Stdout, os.Stdout, os.Stdout)
} else {
// if debug env is not present or false, do not log debug output to console
InitLogging(os.Stdout, ioutil.Discard, os.Stdout)
}
RunAPITests(t, router, healthRouteTests[0:1])
}

View File

@ -0,0 +1,11 @@
package tripsgo
// Healthcheck - Structure for healthcheck response body
type Healthcheck struct {
//
Message string `json:"message,omitempty"`
//
Status string `json:"status,omitempty"`
}

View File

@ -0,0 +1,61 @@
package tripsgo
import (
"fmt"
"io"
"log"
"net/http"
"time"
)
//
var (
Info *log.Logger
Debug *log.Logger
Fatal *log.Logger
)
// InitLogging - Initialize logging for trips api
func InitLogging(
infoHandle io.Writer,
debugHandle io.Writer,
fatalHandle io.Writer) {
Info = log.New(infoHandle,
"INFO: ",
log.Ldate|log.Ltime|log.Lshortfile)
Debug = log.New(debugHandle,
"DEBUG: ",
log.Ldate|log.Ltime|log.Lshortfile)
Fatal = log.New(fatalHandle,
"FATAL: ",
log.Ldate|log.Ltime|log.Lshortfile)
}
// Logger - basic console logger that writes request info to stdout
func Logger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner.ServeHTTP(w, r)
Info.Println(fmt.Sprintf(
"%s %s %s %s",
r.Method,
r.RequestURI,
name,
time.Since(start),
))
})
}
func logMessage(msg string) {
Info.Println(msg)
}
func logError(err error, msg string) {
Info.Println(msg)
Debug.Println(err.Error())
}

View File

@ -0,0 +1,58 @@
package tripsgo
import (
"bytes"
"errors"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLogMessagePrintToInfoLogUnit(t *testing.T) {
//arrange
info := new(bytes.Buffer)
debug := new(bytes.Buffer)
fatal := new(bytes.Buffer)
InitLogging(info, debug, fatal)
errorMessage := "This is a test message"
//act
logMessage(errorMessage)
//assert
actual := fmt.Sprint(info)
assert.True(t, strings.Contains(actual, errorMessage))
}
func TestLogErrorPrintsMsgToInfoUnit(t *testing.T) {
//arrange
info := new(bytes.Buffer)
debug := new(bytes.Buffer)
fatal := new(bytes.Buffer)
InitLogging(info, debug, fatal)
errorMessage := "This is a test message"
err := errors.New("This is a fake error")
//act
logError(err, errorMessage)
//assert
actual := fmt.Sprint(info)
assert.True(t, strings.Contains(actual, errorMessage))
}
func TestLogErrorPrintsErrMessageToDebugUnit(t *testing.T) {
//arrange
info := new(bytes.Buffer)
debug := new(bytes.Buffer)
fatal := new(bytes.Buffer)
InitLogging(info, debug, fatal)
errorMessage := "This is a test message"
err := errors.New("This is a fake error")
//act
logError(err, errorMessage)
//assert
actual := fmt.Sprint(debug)
assert.True(t, strings.Contains(actual, "This is a fake error"))
}

View File

@ -0,0 +1,393 @@
package tripsgo
import (
"fmt"
"strconv"
)
// SelectTripByIDQuery - REQUIRED tripID value
func SelectTripByIDQuery(tripID string) string {
return `SELECT
Id,
Name,
UserId,
RecordedTimeStamp,
EndTimeStamp,
Rating,
IsComplete,
HasSimulatedOBDData,
AverageSpeed,
FuelUsed,
HardStops,
HardAccelerations,
Distance,
CreatedAt,
UpdatedAt
FROM Trips
WHERE Id = '` + tripID + `'
AND Deleted = 0`
}
var SelectAllTripsQuery = selectAllTripsQuery
// SelectAllTripsQuery - select all trips
func selectAllTripsQuery() string {
return `SELECT
Id,
Name,
UserId,
RecordedTimeStamp,
EndTimeStamp,
Rating,
IsComplete,
HasSimulatedOBDData,
AverageSpeed,
FuelUsed,
HardStops,
HardAccelerations,
Distance,
CreatedAt,
UpdatedAt
FROM Trips
WHERE Deleted = 0`
}
// SelectAllTripsForUserQuery REQUIRED userID
var SelectAllTripsForUserQuery = selectAllTripsForUserQuery
func selectAllTripsForUserQuery(userID string) string {
return `SELECT
Id,
Name,
UserId,
RecordedTimeStamp,
EndTimeStamp,
Rating,
IsComplete,
HasSimulatedOBDData,
AverageSpeed,
FuelUsed,
HardStops,
HardAccelerations,
Distance,
CreatedAt,
UpdatedAt
FROM Trips
WHERE UserId ='` + userID + `'
AND Deleted = 0`
}
// DeleteTripPointsForTripQuery - REQUIRED tripID
func DeleteTripPointsForTripQuery(tripID string) string {
return fmt.Sprintf("UPDATE TripPoints SET Deleted = 1 WHERE TripId = '%s'", tripID)
}
// DeleteTripQuery - REQUIRED tripID
func DeleteTripQuery(tripID string) string {
return fmt.Sprintf("UPDAte Trips SET Deleted = 1 WHERE Id = '%s'", tripID)
}
// UpdateTripQuery - REQUIRED trip object and tripID
func UpdateTripQuery(trip Trip) string {
var query = `UPDATE Trips SET
Name = '%s',
UserId = '%s',
RecordedTimeStamp = '%s',
EndTimeStamp = '%s',
Rating = %d,
IsComplete = '%s',
HasSimulatedOBDData = '%s',
AverageSpeed = %g,
FuelUsed = %g,
HardStops = %d,
HardAccelerations = %d,
Distance = %g,
UpdatedAt = GETDATE()
WHERE Id = '%s'`
var formattedQuery = fmt.Sprintf(
query,
trip.Name,
trip.UserID,
trip.RecordedTimeStamp,
trip.EndTimeStamp,
trip.Rating,
strconv.FormatBool(trip.IsComplete),
strconv.FormatBool(trip.HasSimulatedOBDData),
trip.AverageSpeed,
trip.FuelUsed,
trip.HardStops,
trip.HardAccelerations,
trip.Distance,
trip.ID)
Debug.Println("updateTripQuery: " + formattedQuery)
return formattedQuery
}
func createTripQuery(trip Trip) string {
var query = `DECLARE @tempReturn
TABLE (TripId NVARCHAR(128));
INSERT INTO Trips (
Name,
UserId,
RecordedTimeStamp,
EndTimeStamp,
Rating,
IsComplete,
HasSimulatedOBDData,
AverageSpeed,
FuelUsed,
HardStops,
HardAccelerations,
Distance,
UpdatedAt,
Deleted)
OUTPUT Inserted.ID
INTO @tempReturn
VALUES (
'%s',
'%s',
'%s',
'%s',
%d,
'%s',
'%s',
%g,
%g,
%d,
%d,
%g,
GETDATE(),
'false');
SELECT TripId FROM @tempReturn`
var formattedQuery = fmt.Sprintf(
query,
trip.Name,
trip.UserID,
trip.RecordedTimeStamp,
trip.EndTimeStamp,
trip.Rating,
strconv.FormatBool(trip.IsComplete),
strconv.FormatBool(trip.HasSimulatedOBDData),
trip.AverageSpeed,
trip.FuelUsed,
trip.HardStops,
trip.HardAccelerations,
trip.Distance)
Debug.Println("createTripQuery: " + formattedQuery)
return formattedQuery
}
func selectTripPointsForTripQuery(tripID string) string {
var query = `SELECT
[Id],
[TripId],
[Latitude],
[Longitude],
[Speed],
[RecordedTimeStamp],
[Sequence],
[RPM],
[ShortTermFuelBank],
[LongTermFuelBank],
[ThrottlePosition],
[RelativeThrottlePosition],
[Runtime],
[DistanceWithMalfunctionLight],
[EngineLoad],
[EngineFuelRate],
[VIN]
FROM [dbo].[TripPoints]
WHERE
TripId = '%s'
AND Deleted = 0`
var formattedQuery = fmt.Sprintf(
query,
tripID)
Debug.Println("selectTripPointsForTripQuery: " + formattedQuery)
return formattedQuery
}
func selectTripPointsForTripPointIDQuery(tripPointID string) string {
var query = `SELECT
[Id],
[TripId],
[Latitude],
[Longitude],
[Speed],
[RecordedTimeStamp],
[Sequence],
[RPM],
[ShortTermFuelBank],
[LongTermFuelBank],
[ThrottlePosition],
[RelativeThrottlePosition],
[Runtime],
[DistanceWithMalfunctionLight],
[EngineLoad],
[EngineFuelRate],
[VIN]
FROM TripPoints
WHERE Id = '%s'
AND Deleted = 0`
var formattedQuery = fmt.Sprintf(
query,
tripPointID)
Debug.Println("selectTripPointsForTripPointIDQuery: " + formattedQuery)
return formattedQuery
}
func createTripPointQuery(tripPoint TripPoint, tripID string) string {
var query = `DECLARE @tempReturn TABLE (TripPointId NVARCHAR(128));
INSERT INTO TripPoints (
[TripId],
[Latitude],
[Longitude],
[Speed],
[RecordedTimeStamp],
[Sequence],
[RPM],
[ShortTermFuelBank],
[LongTermFuelBank],
[ThrottlePosition],
[RelativeThrottlePosition],
[Runtime],
[DistanceWithMalfunctionLight],
[EngineLoad],
[EngineFuelRate],
[MassFlowRate],
[HasOBDData],
[HasSimulatedOBDData],
[VIN],
[UpdatedAt],
[Deleted])
OUTPUT
Inserted.ID
INTO @tempReturn
VALUES (
'%s',
%g,
%g,
%g,
'%s',
%d,
%g,
%g,
%g,
%g,
%g,
%g,
%g,
%g,
%g,
%g,
'%s',
'%s',
'%s',
GETDATE(),
'false');
SELECT TripPointId
FROM @tempReturn`
var formattedQuery = fmt.Sprintf(
query,
tripID,
tripPoint.Latitude,
tripPoint.Longitude,
tripPoint.Speed,
tripPoint.RecordedTimeStamp,
tripPoint.Sequence,
tripPoint.RPM,
tripPoint.ShortTermFuelBank,
tripPoint.LongTermFuelBank,
tripPoint.ThrottlePosition,
tripPoint.RelativeThrottlePosition,
tripPoint.Runtime,
tripPoint.DistanceWithMalfunctionLight,
tripPoint.EngineLoad,
tripPoint.MassFlowRate,
tripPoint.EngineFuelRate,
strconv.FormatBool(tripPoint.HasOBDData),
strconv.FormatBool(tripPoint.HasSimulatedOBDData),
tripPoint.VIN)
Debug.Println("createTripPointQuery: " + formattedQuery)
return formattedQuery
}
func updateTripPointQuery(tripPoint TripPoint) string {
var query = `UPDATE [TripPoints]
SET [TripId] = '%s',
[Latitude] = '%s',
[Longitude] = '%s',
[Speed] = '%s',
[RecordedTimeStamp] = '%s',
[Sequence] = %d,[RPM] = '%s',
[ShortTermFuelBank] = '%s',
[LongTermFuelBank] = '%s',
[ThrottlePosition] = '%s',
[RelativeThrottlePosition] = '%s',
[Runtime] = '%s',
[DistanceWithMalfunctionLight] = '%s',
[EngineLoad] = '%s',
[MassFlowRate] = '%s',
[EngineFuelRate] = '%s',
[HasOBDData] = '%s',
[HasSimulatedOBDData] = '%s',
[VIN] = '%s'
WHERE Id = '%s'`
var formattedQuery = fmt.Sprintf(
query,
tripPoint.TripID,
tripPoint.Latitude,
tripPoint.Longitude,
tripPoint.Speed,
tripPoint.RecordedTimeStamp,
tripPoint.Sequence,
tripPoint.RPM,
tripPoint.ShortTermFuelBank,
tripPoint.LongTermFuelBank,
tripPoint.ThrottlePosition,
tripPoint.RelativeThrottlePosition,
tripPoint.Runtime,
tripPoint.DistanceWithMalfunctionLight,
tripPoint.EngineLoad,
tripPoint.MassFlowRate,
tripPoint.EngineFuelRate,
strconv.FormatBool(tripPoint.HasOBDData),
strconv.FormatBool(tripPoint.HasSimulatedOBDData),
tripPoint.VIN,
tripPoint.ID)
Debug.Println("updateTripPointQuery: " + formattedQuery)
return formattedQuery
}
func deleteTripPointQuery(tripPointID string) string {
var query = `UPDATE TripPoints
SET Deleted = 1
WHERE Id = '%s'`
var formattedQuery = fmt.Sprintf(
query,
tripPointID)
Debug.Println("deleteTripPointQuery: " + formattedQuery)
return formattedQuery
}

View File

@ -0,0 +1,416 @@
package tripsgo
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestUnitdeleteTripPointQuery(t *testing.T) {
//arrange
var expected = `UPDATE TripPoints
SET Deleted = 1
WHERE Id = '1234'`
//act
query := deleteTripPointQuery("1234")
//assert
if query != expected {
t.Errorf("Error \nExpected: %s \nGot: %s", expected, query)
}
}
func TestUnitupdateTripPointQuery(t *testing.T) {
//arrange
tripPoint := TripPoint{
ID: "abcd",
TripID: "a_trip",
Latitude: 51.5244282,
Longitude: -0.0784379,
Speed: 185.2,
RecordedTimeStamp: "a_timestamp",
Sequence: 1,
RPM: 4000,
ShortTermFuelBank: 1,
LongTermFuelBank: 2,
ThrottlePosition: 3,
RelativeThrottlePosition: 4,
Runtime: 5,
DistanceWithMalfunctionLight: 6,
EngineLoad: 7,
MassFlowRate: 8,
EngineFuelRate: 9,
HasOBDData: true,
HasSimulatedOBDData: false,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Deleted: false,
}
var expected = `UPDATE [TripPoints]
SET [TripId] = 'a_trip',
[Latitude] = '%!s(float32=51.52443)',
[Longitude] = '%!s(float32=-0.0784379)',
[Speed] = '%!s(float32=185.2)',
[RecordedTimeStamp] = 'a_timestamp',
[Sequence] = 1,[RPM] = '%!s(float32=4000)',
[ShortTermFuelBank] = '%!s(float32=1)',
[LongTermFuelBank] = '%!s(float32=2)',
[ThrottlePosition] = '%!s(float32=3)',
[RelativeThrottlePosition] = '%!s(float32=4)',
[Runtime] = '%!s(float32=5)',
[DistanceWithMalfunctionLight] = '%!s(float32=6)',
[EngineLoad] = '%!s(float32=7)',
[MassFlowRate] = '%!s(float32=8)',
[EngineFuelRate] = '%!s(float32=9)',
[HasOBDData] = 'true',
[HasSimulatedOBDData] = 'false',
[VIN] = '{ %!s(bool=false)}'
WHERE Id = 'abcd'`
//act
query := updateTripPointQuery(tripPoint)
//assert
assert.Equal(t, expected, query)
}
func TestSelectAllTripsQueryUnit(t *testing.T) {
//arrange
var expected = `SELECT
Id,
Name,
UserId,
RecordedTimeStamp,
EndTimeStamp,
Rating,
IsComplete,
HasSimulatedOBDData,
AverageSpeed,
FuelUsed,
HardStops,
HardAccelerations,
Distance,
CreatedAt,
UpdatedAt
FROM Trips
WHERE Deleted = 0`
//act
query := SelectAllTripsQuery()
//assert
assert.Equal(t, expected, query)
}
func TestSelectAllTripsForUserQueryUnit(t *testing.T) {
//arrange
var expected = `SELECT
Id,
Name,
UserId,
RecordedTimeStamp,
EndTimeStamp,
Rating,
IsComplete,
HasSimulatedOBDData,
AverageSpeed,
FuelUsed,
HardStops,
HardAccelerations,
Distance,
CreatedAt,
UpdatedAt
FROM Trips
WHERE UserId ='fake_user'
AND Deleted = 0`
//act
query := SelectAllTripsForUserQuery("fake_user")
//assert
assert.Equal(t, expected, query)
}
func TestDeleteTripPointsForTripQueryUnit(t *testing.T) {
//arrange
var expected = `UPDATE TripPoints SET Deleted = 1 WHERE TripId = 'trip_123'`
//act
query := DeleteTripPointsForTripQuery("trip_123")
//assert
assert.Equal(t, expected, query)
}
func TestDeleteTripQueryUnit(t *testing.T) {
//arrange
var expected = `UPDAte Trips SET Deleted = 1 WHERE Id = 'trip_123'`
//act
query := DeleteTripQuery("trip_123")
//assert
assert.Equal(t, expected, query)
}
func TestSelectTripByIDQueryUnit(t *testing.T) {
//arrange
var expected = `SELECT
Id,
Name,
UserId,
RecordedTimeStamp,
EndTimeStamp,
Rating,
IsComplete,
HasSimulatedOBDData,
AverageSpeed,
FuelUsed,
HardStops,
HardAccelerations,
Distance,
CreatedAt,
UpdatedAt
FROM Trips
WHERE Id = 'trip_123'
AND Deleted = 0`
//act
query := SelectTripByIDQuery("trip_123")
//assert
assert.Equal(t, expected, query)
}
func TestSelectTripPointsForTripPointIDQueryUnit(t *testing.T) {
//arrange
var expected = `SELECT
[Id],
[TripId],
[Latitude],
[Longitude],
[Speed],
[RecordedTimeStamp],
[Sequence],
[RPM],
[ShortTermFuelBank],
[LongTermFuelBank],
[ThrottlePosition],
[RelativeThrottlePosition],
[Runtime],
[DistanceWithMalfunctionLight],
[EngineLoad],
[EngineFuelRate],
[VIN]
FROM TripPoints
WHERE Id = 'point_ab'
AND Deleted = 0`
//act
query := selectTripPointsForTripPointIDQuery("point_ab")
//assert
assert.Equal(t, expected, query)
}
func TestUpdateTripQueryUnit(t *testing.T) {
//arrange
trip := Trip{
ID: "abcd",
Name: "fake Trip",
UserID: "fake user",
RecordedTimeStamp: "now",
EndTimeStamp: "then",
Rating: 1,
IsComplete: false,
HasSimulatedOBDData: false,
AverageSpeed: 88,
FuelUsed: 23.2,
HardStops: 8,
HardAccelerations: 12,
Distance: 5,
Created: time.Now(),
UpdatedAt: time.Now(),
Deleted: false,
}
var expected = `UPDATE Trips SET
Name = 'fake Trip',
UserId = 'fake user',
RecordedTimeStamp = 'now',
EndTimeStamp = 'then',
Rating = 1,
IsComplete = 'false',
HasSimulatedOBDData = 'false',
AverageSpeed = 88,
FuelUsed = 23.2,
HardStops = 8,
HardAccelerations = 12,
Distance = 5,
UpdatedAt = GETDATE()
WHERE Id = 'abcd'`
//act
query := UpdateTripQuery(trip)
//assert
assert.Equal(t, expected, query)
}
func TestCreateTripQueryUnit(t *testing.T) {
//arrange
trip := Trip{
ID: "abcd",
Name: "fake Trip",
UserID: "fake user",
RecordedTimeStamp: "now",
EndTimeStamp: "then",
Rating: 1,
IsComplete: false,
HasSimulatedOBDData: false,
AverageSpeed: 88,
FuelUsed: 23.2,
HardStops: 8,
HardAccelerations: 12,
Distance: 5,
Created: time.Now(),
UpdatedAt: time.Now(),
Deleted: false,
}
var expected = `DECLARE @tempReturn
TABLE (TripId NVARCHAR(128));
INSERT INTO Trips (
Name,
UserId,
RecordedTimeStamp,
EndTimeStamp,
Rating,
IsComplete,
HasSimulatedOBDData,
AverageSpeed,
FuelUsed,
HardStops,
HardAccelerations,
Distance,
UpdatedAt,
Deleted)
OUTPUT Inserted.ID
INTO @tempReturn
VALUES (
'fake Trip',
'fake user',
'now',
'then',
1,
'false',
'false',
88,
23.2,
8,
12,
5,
GETDATE(),
'false');
SELECT TripId FROM @tempReturn`
//act
query := createTripQuery(trip)
//assert
assert.Equal(t, expected, query)
}
func TestSelectTripPointsForTripQueryUnit(t *testing.T) {
//arrange
var expected = `SELECT
[Id],
[TripId],
[Latitude],
[Longitude],
[Speed],
[RecordedTimeStamp],
[Sequence],
[RPM],
[ShortTermFuelBank],
[LongTermFuelBank],
[ThrottlePosition],
[RelativeThrottlePosition],
[Runtime],
[DistanceWithMalfunctionLight],
[EngineLoad],
[EngineFuelRate],
[VIN]
FROM [dbo].[TripPoints]
WHERE
TripId = 'trip_zzyzx'
AND Deleted = 0`
//act
query := selectTripPointsForTripQuery("trip_zzyzx")
//assert
assert.Equal(t, expected, query)
}
func TestCreateTripPointQueryUnit(t *testing.T) {
//arrange
tripPoint := TripPoint{
ID: "abcd",
TripID: "a_trip",
Latitude: 51.5244282,
Longitude: -0.0784379,
Speed: 185.2,
RecordedTimeStamp: "a_timestamp",
Sequence: 1,
RPM: 4000,
ShortTermFuelBank: 1,
LongTermFuelBank: 2,
ThrottlePosition: 3,
RelativeThrottlePosition: 4,
Runtime: 5,
DistanceWithMalfunctionLight: 6,
EngineLoad: 7,
MassFlowRate: 8,
EngineFuelRate: 9,
HasOBDData: true,
HasSimulatedOBDData: false,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Deleted: false,
}
var expected = `DECLARE @tempReturn TABLE (TripPointId NVARCHAR(128));
INSERT INTO TripPoints (
[TripId],
[Latitude],
[Longitude],
[Speed],
[RecordedTimeStamp],
[Sequence],
[RPM],
[ShortTermFuelBank],
[LongTermFuelBank],
[ThrottlePosition],
[RelativeThrottlePosition],
[Runtime],
[DistanceWithMalfunctionLight],
[EngineLoad],
[EngineFuelRate],
[MassFlowRate],
[HasOBDData],
[HasSimulatedOBDData],
[VIN],
[UpdatedAt],
[Deleted])
OUTPUT
Inserted.ID
INTO @tempReturn
VALUES (
'fake_trip_id',
51.52443,
-0.0784379,
185.2,
'a_timestamp',
1,
4000,
1,
2,
3,
4,
5,
6,
7,
8,
9,
'true',
'false',
'{ %!s(bool=false)}',
GETDATE(),
'false');
SELECT TripPointId
FROM @tempReturn`
//act
query := createTripPointQuery(tripPoint, "fake_trip_id")
//assert
assert.Equal(t, expected, query)
}

View File

@ -0,0 +1,203 @@
package tripsgo
import (
"flag"
"fmt"
"net/http"
"os"
"github.com/codemodus/swagui"
"github.com/codemodus/swagui/suidata3"
"github.com/gorilla/mux"
)
var (
du = flag.String("du", getEnv("DOCS_URI", "http://localhost:8080"), "docs endpoint")
wsbu = flag.String("wsbu", getEnv("WEB_SERVER_BASE_URI", "changeme"), "base portion of server uri")
)
// Route - object representing a route handler
type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}
// Routes - Route handler collection
type Routes []Route
// NewRouter - Constructor
func NewRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
for _, route := range routes {
CreateHandler(router, route)
}
// add docs route
CreateDocsHandler(router, docsRoute)
return router
}
// Index - Default route handler for service base uri
func Index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Trips Service")
}
// CreateHandler - Create router handler
func CreateHandler(router *mux.Router, route Route) {
var handler http.Handler
handler = route.HandlerFunc
handler = Logger(handler, route.Name)
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(handler)
}
// CreateDocsHandler - Create route handler for docs using SwagUI
func CreateDocsHandler(router *mux.Router, route Route) {
var def = "/api/json/swagger.json"
var provider = suidata3.New()
ui, err := swagui.New(http.NotFoundHandler(), provider)
if err != nil {
Info.Println(err)
os.Exit(1)
}
router.
Methods(route.Method).
Name(route.Name).
Handler(ui.Handler(def))
router.
Methods(route.Method).
Path("/api/docs/trips/{dir}/{fileName}").
Name("*").
Handler(ui.Handler(def))
router.
Methods(route.Method).
Path("/api/docs/trips/{fileName}").
Name("Swagger UI JS").
Handler(ui.Handler(def))
}
var docsRoute = Route{
"swagger-ui",
"GET",
"/api/docs/trips/",
nil,
}
var routes = Routes{
Route{
"Index",
"GET",
"/api/",
Index,
},
Route{
"swagger-json",
"GET",
"/api/json/swagger.json",
swaggerDocsJSON,
},
Route{
"CreateTrip",
"POST",
"/api/trips",
createTrip,
},
Route{
"CreateTripPoint",
"POST",
"/api/trips/{tripID}/trippoints",
createTripPoint,
},
Route{
"DeleteTrip",
"DELETE",
"/api/trips/{tripID}",
deleteTrip,
},
Route{
"DeleteTripPoint",
"DELETE",
"/api/trips/{tripID}/trippoints/{tripPointID}",
deleteTripPoint,
},
Route{
"GetAllTrips",
"GET",
"/api/trips",
getAllTrips,
},
Route{
"GetAllTripsForUser",
"GET",
"/api/trips/user/{userID}",
getAllTripsForUser,
},
Route{
"GetTripById",
"GET",
"/api/trips/{tripID}",
getTripByID,
},
Route{
"GetTripPointByID",
"GET",
"/api/trips/{tripID}/trippoints/{tripPointID}",
getTripPointByID,
},
Route{
"GetTripPoints",
"GET",
"/api/trips/{tripID}/trippoints",
getTripPoints,
},
Route{
"HealthcheckGet",
"GET",
"/api/healthcheck/trips",
healthcheckGet,
},
Route{
"UpdateTrip",
"PATCH",
"/api/trips/{tripID}",
updateTrip,
},
Route{
"UpdateTripPoint",
"PATCH",
"/api/trips/{tripID}/trippoints/{tripPointID}",
updateTripPoint,
},
Route{
"VersionGet",
"GET",
"/api/version/trips",
versionGet,
},
}

View File

@ -0,0 +1,28 @@
package tripsgo
import (
"fmt"
"net/http"
"os"
"time"
)
func getSwaggerJsonPath() string {
if value, ok := os.LookupEnv("SWAGGER_JSON_PATH"); ok {
return value
}
return "./api/swagger.json"
}
func swaggerDocsJSON(w http.ResponseWriter, r *http.Request) {
swaggerPath := getSwaggerJsonPath()
fData, err := os.Open(swaggerPath)
if err != nil {
var msg = fmt.Sprintf("swaggerDocsJson - Unable to open and read swagger.json : %v", err)
w.WriteHeader(http.StatusInternalServerError)
Info.Println(msg)
http.Error(w, msg, -1)
return
}
http.ServeContent(w, r, "swagger.json", time.Now(), fData)
}

View File

@ -0,0 +1,49 @@
package tripsgo
import (
"io/ioutil"
"os"
"testing"
)
func TestSwaggerServiceSuccessUnit(t *testing.T) {
router := NewRouter()
os.Setenv("SWAGGER_JSON_PATH", "../api/swagger.json")
var debug, present = os.LookupEnv("DEBUG_LOGGING")
if present && debug == "true" {
InitLogging(os.Stdout, os.Stdout, os.Stdout)
} else {
// if debug env is not present or false, do not log debug output to console
InitLogging(os.Stdout, ioutil.Discard, os.Stdout)
}
RunAPITests(t, router, []APITestCase{
{
Tag: "swaggerService",
Method: "GET",
URL: "/api/json/swagger.json",
Status: 200,
},
})
}
func TestSwaggerServiceFailUnit(t *testing.T) {
router := NewRouter()
os.Unsetenv("SWAGGER_JSON_PATH")
var debug, present = os.LookupEnv("DEBUG_LOGGING")
if present && debug == "true" {
InitLogging(os.Stdout, os.Stdout, os.Stdout)
} else {
// if debug env is not present or false, do not log debug output to console
InitLogging(os.Stdout, ioutil.Discard, os.Stdout)
}
RunAPITestsPlainText(t, router, []APITestCase{
{
Tag: "swaggerService",
Method: "GET",
URL: "/api/json/swagger.json",
Status: 500,
},
})
}

View File

@ -0,0 +1,66 @@
package tripsgo
import (
"bytes"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
"github.com/stretchr/testify/assert"
)
// APITestCase needs to be exported to be accessed for test dir
type APITestCase struct {
Tag string
Method string
URL string
Body string
Status int
ExpectedResponse string
ActualResponse string
}
func testAPI(router *mux.Router, method, URL, body string) *httptest.ResponseRecorder {
req, _ := http.NewRequest(method, URL, bytes.NewBufferString(body))
req.Header.Set("Content-Type", "application/json")
res := httptest.NewRecorder()
router.ServeHTTP(res, req)
return res
}
// RunAPITests needs to be exported to be accessed for test dir
func RunAPITests(t *testing.T, router *mux.Router, tests []APITestCase) {
for i := 0; i < len(tests); i++ {
res := testAPI(router, tests[i].Method, tests[i].URL, tests[i].Body)
tests[i].ActualResponse = res.Body.String()
Debug.Println(tests[i].Tag + " - " + tests[i].ActualResponse)
assert.Equal(t, tests[i].Status, res.Code, tests[i].Tag)
Info.Println(tests[i].Tag + "- Response Code:" + strconv.Itoa(res.Code))
if tests[i].ExpectedResponse != "" {
assert.JSONEq(t, tests[i].ExpectedResponse, res.Body.String(), tests[i].Tag)
}
}
}
func RunAPITestsPlainText(t *testing.T, router *mux.Router, tests []APITestCase) {
for i := 0; i < len(tests); i++ {
res := testAPI(router, tests[i].Method, tests[i].URL, tests[i].Body)
tests[i].ActualResponse = res.Body.String()
Debug.Println(tests[i].Tag + " - " + tests[i].ActualResponse)
assert.Equal(t, tests[i].Status, res.Code, tests[i].Tag)
Info.Println(tests[i].Tag + "- Response Code:" + strconv.Itoa(res.Code))
if tests[i].ExpectedResponse != "" {
assert.Equal(t, tests[i].ExpectedResponse, res.Body.String(), tests[i].Tag)
}
}
}
func resetDataAccessEnvVars() {
var fls bool = false
debug = &fls
godotenv.Overload()
RebindDataAccessEnvironmentVariables()
}

View File

@ -0,0 +1,43 @@
package tripsgo
import (
"time"
)
// Trip - Represents a single trip by a user with its associated set of trip points.
type Trip struct {
// Trip ID
ID string `json:"Id"`
Name string `json:"Name"`
// User's unique identity
UserID string `json:"UserId"`
RecordedTimeStamp string `json:"RecordedTimeStamp"`
EndTimeStamp string `json:"EndTimeStamp"`
Rating int32 `json:"Rating"`
IsComplete bool `json:"IsComplete"`
HasSimulatedOBDData bool `json:"HasSimulatedOBDData"`
AverageSpeed float32 `json:"AverageSpeed"`
FuelUsed float32 `json:"FuelUsed"`
HardStops int64 `json:"HardStops"`
HardAccelerations int64 `json:"HardAccelerations"`
Distance float32 `json:"Distance"`
Created time.Time `json:"Created"`
UpdatedAt time.Time `json:"UpdatedAt"`
Deleted bool `json:"Deleted,omitempty"`
}

View File

@ -0,0 +1,258 @@
package tripsgo
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/gorilla/mux"
)
// TripPoint Service Methods
func getTripPoints(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
var tripID = params["tripID"]
var query = selectTripPointsForTripQuery(tripID)
statement, err := ExecuteQuery(query)
if err != nil {
var msg = "Error while retrieving trip points from database"
logError(err, msg)
fmt.Fprintf(w, SerializeError(err, msg))
return
}
tripPointRows := []TripPoint{}
for statement.Next() {
var tp TripPoint
err := statement.Scan(
&tp.ID,
&tp.TripID,
&tp.Latitude,
&tp.Longitude,
&tp.Speed,
&tp.RecordedTimeStamp,
&tp.Sequence,
&tp.RPM,
&tp.ShortTermFuelBank,
&tp.LongTermFuelBank,
&tp.ThrottlePosition,
&tp.RelativeThrottlePosition,
&tp.Runtime,
&tp.DistanceWithMalfunctionLight,
&tp.EngineLoad,
&tp.EngineFuelRate,
&tp.VIN)
if err != nil {
var msg = "Error scanning Trip Points"
logError(err, msg)
fmt.Fprintf(w, SerializeError(err, msg))
return
}
tripPointRows = append(tripPointRows, tp)
}
serializedReturn, _ := json.Marshal(tripPointRows)
fmt.Fprintf(w, string(serializedReturn))
}
func getTripPointByID(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
tripPointID := params["tripPointID"]
var query = selectTripPointsForTripPointIDQuery(tripPointID)
row, err := FirstOrDefault(query)
if err != nil {
var msg = "Error while retrieving trip point from database"
logError(err, msg)
fmt.Fprintf(w, SerializeError(err, msg))
return
}
var tripPoint TripPoint
err = row.Scan(
&tripPoint.ID,
&tripPoint.TripID,
&tripPoint.Latitude,
&tripPoint.Longitude,
&tripPoint.Speed,
&tripPoint.RecordedTimeStamp,
&tripPoint.Sequence,
&tripPoint.RPM,
&tripPoint.ShortTermFuelBank,
&tripPoint.LongTermFuelBank,
&tripPoint.ThrottlePosition,
&tripPoint.RelativeThrottlePosition,
&tripPoint.Runtime,
&tripPoint.DistanceWithMalfunctionLight,
&tripPoint.EngineLoad,
&tripPoint.EngineFuelRate,
&tripPoint.VIN)
if err != nil {
var msg = "Failed to scan a trip point"
logError(err, msg)
fmt.Fprintf(w, SerializeError(err, msg))
return
}
serializedTripPoint, _ := json.Marshal(tripPoint)
fmt.Fprintf(w, string(serializedTripPoint))
}
func createTripPoint(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
tripID := params["tripID"]
body, err := ioutil.ReadAll(r.Body)
var tripPoint TripPoint
err = json.Unmarshal(body, &tripPoint)
if err != nil {
var msg = "Error while decoding json for trip point"
logError(err, msg)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, SerializeError(err, msg))
return
}
var query = createTripPointQuery(tripPoint, tripID)
result, err := ExecuteQuery(query)
if err != nil {
var msg = "Error while inserting Trip Point into database"
logError(err, msg)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, SerializeError(err, msg))
return
}
for result.Next() {
err = result.Scan(&tripPoint.ID)
if err != nil {
var msg = "Error retrieving trip point id"
logError(err, msg)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, SerializeError(err, msg))
}
}
serializedTripPoint, _ := json.Marshal(tripPoint)
fmt.Fprintf(w, string(serializedTripPoint))
}
func updateTripPoint(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
tripPointID := params["tripPointID"]
body, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if err != nil {
var msg = "Error while decoding json for trip point"
logError(err, msg)
fmt.Fprintf(w, SerializeError(err, msg))
return
}
var tripPoint TripPoint
err = json.Unmarshal(body, &tripPoint)
if err != nil {
var msg = "Error while decoding json"
logError(err, msg)
fmt.Fprintf(w, SerializeError(err, msg))
return
}
tripPoint.ID = tripPointID
var query = updateTripPointQuery(tripPoint)
result, err := ExecuteNonQuery(query)
if err != nil {
var msg = "Error while patching Trip Point on the database"
logError(err, msg)
fmt.Fprintf(w, SerializeError(err, msg))
return
}
fmt.Fprintf(w, string(result))
}
func deleteTripPoint(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
tripPointID := params["tripPointID"]
var query = deleteTripPointQuery(tripPointID)
result, err := ExecuteNonQuery(query)
if err != nil {
var msg = "Error while deleting trip point from database"
logError(err, msg)
fmt.Fprintf(w, SerializeError(err, msg))
return
}
serializedResult, _ := json.Marshal(result)
fmt.Fprintf(w, string(serializedResult))
}
// func getMaxSequence(w http.ResponseWriter, r *http.Request) {
// tripID := r.FormValue("id")
// query := fmt.Sprintf("SELECT MAX(Sequence) as MaxSequence FROM TripPoints where tripid = '%s'", tripID)
// row, err := FirstOrDefault(query)
// if err != nil {
// var msg = "Error while querying Max Sequence"
// logError(err, msg)
// fmt.Fprintf(w, SerializeError(err, msg))
// return
// }
// var MaxSequence string
// err = row.Scan(&MaxSequence)
// if err != nil {
// var msg = "Error while obtaining max sequence"
// logError(err, msg)
// fmt.Fprintf(w, SerializeError(err, msg))
// return
// }
// fmt.Fprintf(w, MaxSequence)
// }
type newTripPoint struct {
ID string
}

View File

@ -0,0 +1,295 @@
package tripsgo
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
_ "github.com/denisenkom/go-mssqldb" //vscode deletes this import if it is not a blank import
"github.com/gorilla/mux"
)
// Trip Service Methods
// getTripByID - gets a trip by its trip id
func getTripByID(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
//Build Query
var query = SelectTripByIDQuery(params["tripID"])
//Execute Query
row, err := FirstOrDefault(query)
if err != nil {
var msg = "getTripsByID - Error while retrieving trip from database"
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
var trip Trip
errScan := row.Scan(
&trip.ID,
&trip.Name,
&trip.UserID,
&trip.RecordedTimeStamp,
&trip.EndTimeStamp,
&trip.Rating,
&trip.IsComplete,
&trip.HasSimulatedOBDData,
&trip.AverageSpeed,
&trip.FuelUsed,
&trip.HardStops,
&trip.HardAccelerations,
&trip.Distance,
&trip.Created,
&trip.UpdatedAt)
if errScan != nil {
var msg = fmt.Sprintf("No trip with ID '%s' found", params["tripID"])
logMessage(msg)
// fmt.Fprintf(w, msg)
http.NotFound(w, r)
return
}
serializedTrip, _ := json.Marshal(trip)
fmt.Fprintf(w, string(serializedTrip))
}
// getAllTrips - get all trips
func getAllTrips(w http.ResponseWriter, r *http.Request) {
var query = SelectAllTripsQuery()
tripRows, err := ExecuteQuery(query)
if err != nil {
var msg = "getAllTrips - Query Failed to Execute."
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
trips := []Trip{}
for tripRows.Next() {
var r Trip
err := tripRows.Scan(
&r.ID,
&r.Name,
&r.UserID,
&r.RecordedTimeStamp,
&r.EndTimeStamp,
&r.Rating,
&r.IsComplete,
&r.HasSimulatedOBDData,
&r.AverageSpeed,
&r.FuelUsed,
&r.HardStops,
&r.HardAccelerations,
&r.Distance,
&r.Created,
&r.UpdatedAt)
if err != nil {
var msg = "GetAllTrips - Error scanning Trips"
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
trips = append(trips, r)
}
tripsJSON, _ := json.Marshal(trips)
fmt.Fprintf(w, string(tripsJSON))
}
// getAllTripsForUser - get all trips for a given user
func getAllTripsForUser(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
var query = SelectAllTripsForUserQuery(params["userID"])
tripRows, err := ExecuteQuery(query)
if err != nil {
var msg = "getAllTripsForUser - Error while retrieving trips from database"
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
trips := []Trip{}
for tripRows.Next() {
var r Trip
err := tripRows.Scan(&r.ID,
&r.Name,
&r.UserID,
&r.RecordedTimeStamp,
&r.EndTimeStamp,
&r.Rating,
&r.IsComplete,
&r.HasSimulatedOBDData,
&r.AverageSpeed,
&r.FuelUsed,
&r.HardStops,
&r.HardAccelerations,
&r.Distance,
&r.Created,
&r.UpdatedAt)
if err != nil {
var msg = "getAllTripsForUser - Error scanning Trips"
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
trips = append(trips, r)
}
tripsJSON, _ := json.Marshal(trips)
fmt.Fprintf(w, string(tripsJSON))
}
// deleteTrip - deletes a single trip and its associated trip points for a user
func deleteTrip(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
var deleteTripPointsQuery = DeleteTripPointsForTripQuery(params["tripID"])
var deleteTripsQuery = DeleteTripQuery(params["tripID"])
result, err := ExecuteNonQuery(deleteTripPointsQuery)
if err != nil {
var msg = "Error while deleting trip points from database"
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
// Debug.Println(fmt.Sprintln(`Deleted trip points for Trip '%s'`, params["tripID"]))
result, err = ExecuteNonQuery(deleteTripsQuery)
if err != nil {
var msg = "Error while deleting trip from database"
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
// Debug.Println(fmt.Sprintln("Deleted trip '%s'", params["tripID"]))
serializedResult, _ := json.Marshal(result)
fmt.Fprintf(w, string(serializedResult))
}
// updateTrip - update a trip
func updateTrip(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
tripID := params["tripID"]
var trip Trip
body, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if err != nil {
var msg = "Update Trip - Error reading trip request body"
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
err = json.Unmarshal(body, &trip)
if err != nil {
var msg = "Update Trip - Error while decoding trip json"
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
trip.ID = tripID
updateQuery := UpdateTripQuery(trip)
result, err := ExecuteNonQuery(updateQuery)
if err != nil {
var msg = "Error updating trip on the database." + string(result)
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
serializedTrip, _ := json.Marshal(trip)
fmt.Fprintf(w, string(serializedTrip))
}
// createTrip - create a trip for a user. This method does not create the associated trip points, only the trip.
func createTrip(w http.ResponseWriter, r *http.Request) {
//params := mux.Vars(r)
body, err := ioutil.ReadAll(r.Body)
var trip Trip
err = json.Unmarshal(body, &trip)
if err != nil {
var msg = "Error while decoding json"
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
insertQuery := createTripQuery(trip)
var newTripID newTrip
result, err := ExecuteQuery(insertQuery)
if err != nil {
var msg = "Error while inserting trip into database"
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
return
}
for result.Next() {
err = result.Scan(&newTripID.ID)
if err != nil {
var msg = "Error while retrieving last id"
logError(err, msg)
http.Error(w, SerializeError(err, msg), http.StatusInternalServerError)
}
}
trip.ID = newTripID.ID
serializedTrip, _ := json.Marshal(trip)
fmt.Fprintf(w, string(serializedTrip))
}
type newTrip struct {
ID string
}
// End of Trip Service Methods

View File

@ -0,0 +1,436 @@
package tripsgo
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
var tripID string
var apiTestList = []APITestCase{
{
Tag: "t1 - Get all trips",
Method: "GET",
URL: "/api/trips",
Status: 200,
},
{
Tag: "t2 - Get a nonexistent trip",
Method: "GET",
URL: "/api/trips/99999",
Status: 404,
},
{
Tag: "t3 - Create a Trip",
Method: "POST",
URL: "/api/trips",
Body: `{
"Name":"Trip CREATE TEST",
"UserId":"GO_TEST",
"RecordedTimeStamp": "2018-04-19T19:08:16.03Z",
"EndTimeStamp": "2018-04-19T19:42:49.573Z",
"Rating":95,
"IsComplete":false,
"HasSimulatedOBDData":true,
"AverageSpeed":100,
"FuelUsed":10.27193484,
"HardStops":2,
"HardAccelerations":4,
"Distance":30.0275486,
"CreatedAt":"2018-01-01T12:00:00Z",
"UpdatedAt":"2001-01-01T12:00:00Z"
}`,
Status: 200,
},
{
Tag: "t4 - Update a trip",
Method: "PATCH",
URL: "/api/trips/{tripID}",
Body: `{
"Name":"Trip UPDATE TEST",
"UserId":"GO_TEST",
"RecordedTimeStamp": "2018-04-19T19:08:16.03Z",
"EndTimeStamp": "2018-04-19T19:42:49.573Z",
"Rating":91005,
"IsComplete":true,
"HasSimulatedOBDData":true,
"AverageSpeed":100,
"FuelUsed":10.27193484,
"HardStops":2,
"HardAccelerations":4,
"Distance":30.0275486,
"CreatedAt":"2018-01-01T12:00:00Z",
"UpdatedAt":"2001-01-01T12:00:00Z"
}`,
Status: 200,
},
{
Tag: "t5 - Create Trip Point",
Method: "POST",
URL: "/api/trips/{tripID}/trippoints",
Body: `{
"TripId": "{tripID}",
"Latitude": 47.67598,
"Longitude": -122.10612,
"Speed": -255,
"RecordedTimeStamp": "2018-05-24T10:00:15.003Z",
"Sequence": 2,
"RPM": -255,
"ShortTermFuelBank": -255,
"LongTermFuelBank": -255,
"ThrottlePosition": -255,
"RelativeThrottlePosition": -255,
"Runtime": -255,
"DistanceWithMalfunctionLight": -255,
"EngineLoad": -255,
"EngineFuelRate": -255,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z"
}`,
Status: 200,
},
{
Tag: "t6 - Update Trip Point",
Method: "PATCH",
URL: "/api/trips/{tripID}/trippoints/{tripPointID}",
Body: `{
"Id": "{tripPointID}",
"TripId": "{tripID}",
"Latitude": 47.67598,
"Longitude": -122.10612,
"Speed": -255,
"RecordedTimeStamp": "2018-05-24T10:00:15.003Z",
"Sequence": 2,
"RPM": -255,
"ShortTermFuelBank": -255,
"LongTermFuelBank": -255,
"ThrottlePosition": -255,
"RelativeThrottlePosition": -255,
"Runtime": -255,
"DistanceWithMalfunctionLight": -255,
"EngineLoad": -255,
"EngineFuelRate": -255,
"Created": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z"
}`,
ExpectedResponse: "",
Status: 200,
},
{
Tag: "t7 - Read Trip Points for Trip",
Method: "GET",
URL: "/api/trips/{tripID}/trippoints",
Status: 200,
},
{
Tag: "t8 - Read Trip Points By Trip Point ID",
Method: "GET",
URL: "/api/trips/{tripID}/trippoints/{tripPointID}",
Status: 200,
},
{
Tag: "t9 - Delete Trip Point",
Method: "DELETE",
URL: "/api/trips/{tripID}/trippoints/{tripPointID}",
Status: 200,
},
{
Tag: "t10 - Delete a Trip",
Method: "DELETE",
URL: "/api/trips/{tripID}",
Status: 200,
},
{
Tag: "t11 - Get All Trips for User",
Method: "GET",
URL: "/api/trips/user/SomeUser",
Status: 200,
},
}
func TestTripApis(t *testing.T) {
router := NewRouter()
var debug, present = os.LookupEnv("DEBUG_LOGGING")
if present && debug == "true" {
InitLogging(os.Stdout, os.Stdout, os.Stdout)
} else {
// if debug env is not present or false, do not log debug output to console
InitLogging(os.Stdout, ioutil.Discard, os.Stdout)
}
RunAPITests(t, router, apiTestList[0:3])
// setup update trip test (URL, Body, expected Response)
apiTestList[3].URL = strings.Replace(apiTestList[3].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
apiTestList[3].Body = GetUpdateTrip(apiTestList[2].ActualResponse, apiTestList[3].Body)
apiTestList[3].ExpectedResponse = apiTestList[3].Body
// setup create trip point test
apiTestList[4].URL = strings.Replace(apiTestList[4].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
apiTestList[4].Body = strings.Replace(apiTestList[4].Body, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
// run update trip and create trip point tests
RunAPITests(t, router, apiTestList[3:5])
// setup update trip point test
apiTestList[5].URL = strings.Replace(apiTestList[5].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
apiTestList[5].URL = strings.Replace(apiTestList[5].URL, "{tripPointID}", TripPointFromStr(apiTestList[4].ActualResponse).ID, 1)
apiTestList[5].Body = GetUpdateTripPoint(apiTestList[4].ActualResponse, apiTestList[5].Body)
// setup read trip points for trip test
apiTestList[6].URL = strings.Replace(apiTestList[6].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
// // setup ready trip points by trip point id test
apiTestList[7].URL = strings.Replace(apiTestList[7].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
apiTestList[7].URL = strings.Replace(apiTestList[7].URL, "{tripPointID}", TripPointFromStr(apiTestList[4].ActualResponse).ID, 1)
// //setup delete trip point test
apiTestList[8].URL = strings.Replace(apiTestList[8].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
apiTestList[8].URL = strings.Replace(apiTestList[8].URL, "{tripPointID}", TripPointFromStr(apiTestList[4].ActualResponse).ID, 1)
// setup delete test (URL)
apiTestList[9].URL = strings.Replace(apiTestList[9].URL, "{tripID}", TripFromStr(apiTestList[2].ActualResponse).ID, 1)
// run update test
RunAPITests(t, router, apiTestList[5:10])
RunAPITests(t, router, apiTestList[10:11])
}
func TestGetAllTripsReturnsServerErrorIfBadDbConnection(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
os.Setenv("SQL_DRIVER", "not_a_real_driver")
RebindDataAccessEnvironmentVariables()
info := new(bytes.Buffer)
InitLogging(info, os.Stdout, os.Stdout)
var tr bool = true
debug = &tr
//act
router := NewRouter()
RunAPITestsPlainText(t, router, []APITestCase{
{
Tag: "t1 - Get all trips",
Method: "GET",
URL: "/api/trips",
Status: 500,
},
})
//assert
actual := fmt.Sprint(info)
assert.Contains(t, actual, "getAllTrips - Query Failed to Execute")
}
func TestGetAllTripsReturnsErrorScanningTripsIfMissingSqlFields(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
//oldSelectAllTripsQuery := func(SelectAllTripsQuery)
var OldSelectAllTripsQuery = SelectAllTripsQuery
SelectAllTripsQuery = func() string {
return `SELECT
Id
FROM Trips
WHERE Deleted = 0`
}
defer func() { SelectAllTripsQuery = OldSelectAllTripsQuery }()
info := new(bytes.Buffer)
InitLogging(info, os.Stdout, os.Stdout)
var tr bool = true
debug = &tr
//act
router := NewRouter()
RunAPITestsPlainText(t, router, []APITestCase{
{
Tag: "t1 - Get all trips",
Method: "GET",
URL: "/api/trips",
Status: 500,
},
})
//assert
actual := fmt.Sprint(info)
assert.Contains(t, actual, "GetAllTrips - Error scanning Trips")
}
func TestGetAllTripsForUsersReturnsServerErrorIfBadDbConnection(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
os.Setenv("SQL_DRIVER", "not_a_real_driver")
RebindDataAccessEnvironmentVariables()
info := new(bytes.Buffer)
InitLogging(info, os.Stdout, os.Stdout)
var tr bool = true
debug = &tr
//act
router := NewRouter()
RunAPITestsPlainText(t, router, []APITestCase{
{
Tag: "t11 - Get All Trips for User",
Method: "GET",
URL: "/api/trips/user/SomeUser",
Status: 500,
},
})
//assert
actual := fmt.Sprint(info)
assert.Contains(t, actual, "getAllTripsForUser - Error while retrieving trips from database")
}
func TestGetAllTripsForUserReturnsErrorScanningTripsIfMissingSqlFields(t *testing.T) {
defer t.Cleanup(resetDataAccessEnvVars)
//arrange
//oldSelectAllTripsQuery := func(SelectAllTripsQuery)
var OldSelectAllTripsForUserQuery = SelectAllTripsForUserQuery
SelectAllTripsForUserQuery = func(userID string) string {
return `SELECT
Id
FROM Trips
WHERE UserId ='` + userID + `'
AND Deleted = 0`
}
defer func() { SelectAllTripsForUserQuery = OldSelectAllTripsForUserQuery }()
info := new(bytes.Buffer)
InitLogging(info, os.Stdout, os.Stdout)
var tr bool = true
debug = &tr
//act
router := NewRouter()
RunAPITestsPlainText(t, router, []APITestCase{
{
Tag: "t11 - Get All Trips for User",
Method: "GET",
URL: "/api/trips/user/SomeUser",
Status: 500,
},
})
//assert
actual := fmt.Sprint(info)
assert.Contains(t, actual, "getAllTripsForUser - Error scanning Trips")
}
func TestCreateTripReturnsErrorifInvalidJsonBody(t *testing.T) {
info := new(bytes.Buffer)
InitLogging(info, os.Stdout, os.Stdout)
//act
router := NewRouter()
RunAPITestsPlainText(t, router, []APITestCase{
{
Tag: "t3 - Create a Trip",
Method: "POST",
URL: "/api/trips",
Body: `{
"Name":"Trip CREATE TEST",
"UserId":"GO_TEST",
"RecordedTimeStamp": "2018-04-19T19:08:16.03Z",
"EndTimeStamp": "2018-04-19T19:42:49.573Z",
"Rating":95,
"IsComplete":`,
Status: 500,
},
})
//assert
actual := fmt.Sprint(info)
assert.Contains(t, actual, "Error while decoding json")
}
func TestUpdateTripReturnsErrorifInvalidJsonBody(t *testing.T) {
info := new(bytes.Buffer)
InitLogging(info, os.Stdout, os.Stdout)
//act
router := NewRouter()
RunAPITestsPlainText(t, router, []APITestCase{
{
Tag: "t4 - Update a trip",
Method: "PATCH",
URL: "/api/trips/{tripID}",
Body: `{
"Name":"Trip UPDATE TEST",
"UserId":"GO_TEST",
"RecordedTimeStamp": "2018-04-19T19:08:16.03Z",
"EndTimeStamp": "2018-04-19T19:42:49.573Z",
"Rating":`,
Status: 500,
},
})
//assert
actual := fmt.Sprint(info)
assert.Contains(t, actual, "Update Trip - Error while decoding trip json")
}
func GetUpdateTrip(tripCreate string, tripUpdate string) string {
tripC := TripFromStr(tripCreate)
tripU := TripFromStr(tripUpdate)
tripU.ID = tripC.ID
serializedTripUpdate, _ := json.Marshal(tripU)
return string(serializedTripUpdate)
}
func GetUpdateTripPoint(tripPointCreate string, tripPointUpdate string) string {
tripPointC := TripPointFromStr(tripPointCreate)
tripPointU := TripPointFromStr(tripPointUpdate)
tripPointU.ID = tripPointC.ID
tripPointU.TripID = tripPointC.TripID
serializedTripUpdate, _ := json.Marshal(tripPointU)
return string(serializedTripUpdate)
}
func TripFromStr(tripStr string) Trip {
trip := Trip{}
Debug.Println("DEBUG: TripFromStr - " + tripStr)
errCreate := json.Unmarshal([]byte(tripStr), &trip)
if errCreate != nil {
log.Println("TripFromStr - Invalid trip string")
log.Fatal(errCreate)
}
return trip
}
func TripPointFromStr(tripPointStr string) TripPoint {
tripPoint := TripPoint{}
Debug.Println("DEBUG: TripPointFromStr - " + tripPointStr)
errCreate := json.Unmarshal([]byte(tripPointStr), &tripPoint)
if errCreate != nil {
log.Println("TripPointFromStr - Invalid trip point string")
log.Fatal(errCreate)
}
Debug.Println(tripPointStr)
return tripPoint
}

View File

@ -0,0 +1,58 @@
package tripsgo
import (
"database/sql"
"time"
)
// TripPoint - Represents a single point record in a trip
type TripPoint struct {
// Trip Point ID
ID string `json:"Id,omitempty"`
// Trip ID
TripID string `json:"TripId,omitempty"`
Latitude float32 `json:"Latitude,omitempty"`
Longitude float32 `json:"Longitude,omitempty"`
Speed float32 `json:"Speed,omitempty"`
RecordedTimeStamp string `json:"RecordedTimeStamp,omitempty"`
Sequence int32 `json:"Sequence,omitempty"`
RPM float32 `json:"RPM,omitempty"`
ShortTermFuelBank float32 `json:"ShortTermFuelBank,omitempty"`
LongTermFuelBank float32 `json:"LongTermFuelBank,omitempty"`
ThrottlePosition float32 `json:"ThrottlePosition,omitempty"`
RelativeThrottlePosition float32 `json:"RelativeThrottlePosition,omitempty"`
Runtime float32 `json:"Runtime,omitempty"`
DistanceWithMalfunctionLight float32 `json:"DistanceWithMalfunctionLight,omitempty"`
EngineLoad float32 `json:"EngineLoad,omitempty"`
MassFlowRate float32 `json:"MassFlowRate,omitempty"`
EngineFuelRate float32 `json:"EngineFuelRate,omitempty"`
VIN sql.NullString `json:"VIN,omitempty"`
HasOBDData bool `json:"HasOBDData,omitempty"`
HasSimulatedOBDData bool `json:"HasSimulatedOBDData,omitempty"`
CreatedAt time.Time `json:"CreatedAt,omitempty"`
UpdatedAt time.Time `json:"UpdatedAt,omitempty"`
Deleted bool `json:"Deleted,omitempty"`
}

View File

@ -0,0 +1,15 @@
package tripsgo
import (
"fmt"
"net/http"
"os"
)
func versionGet(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
w.WriteHeader(http.StatusOK)
version := os.Getenv("APP_VERSION")
fmt.Fprintf(w, version)
}

View File

@ -0,0 +1,32 @@
package tripsgo
import (
"io/ioutil"
"os"
"testing"
)
var versionRouteTests = []APITestCase{
{
Tag: "versionService",
Method: "GET",
URL: "/api/version/trips",
Status: 200,
ExpectedResponse: `test123`,
},
}
func TestVersionServiceUnit(t *testing.T) {
router := NewRouter()
os.Setenv("APP_VERSION", "test123")
var debug, present = os.LookupEnv("DEBUG_LOGGING")
if present && debug == "true" {
InitLogging(os.Stdout, os.Stdout, os.Stdout)
} else {
// if debug env is not present or false, do not log debug output to console
InitLogging(os.Stdout, ioutil.Discard, os.Stdout)
}
RunAPITestsPlainText(t, router, versionRouteTests[0:1])
}